diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 1f9675ca..e4ddf55a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,7 +37,7 @@ jobs: run: ./dev/fix_dylibs.sh VoxelEngine Release build - name: Run tests - run: ctest --test-dir build + run: ctest --output-on-failure --test-dir build - name: Create DMG run: | diff --git a/.gitignore b/.gitignore index 1847e03c..a1ce6e33 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ appimage-build/ /res/content/* !/res/content/base *.mtl + +# libs +/libs/ +/vcpkg_installed/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a5e839c..f465fcb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,10 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie -lstdc++fs") endif() +if (WIN32) + target_link_libraries(${PROJECT_NAME} VoxelEngineSrc winmm) +endif() + target_link_libraries(${PROJECT_NAME} VoxelEngineSrc ${CMAKE_DL_LIBS}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/doc/en/item-properties.md b/doc/en/item-properties.md index 0ec4bf73..4b10793a 100644 --- a/doc/en/item-properties.md +++ b/doc/en/item-properties.md @@ -12,6 +12,12 @@ Icon type defines a source of an item image displayed in inventory. - **items** (generated from *png* files in *res/textures/items/*) - **block** - block preview. Block ID must be specified in **icon** property. Example: *base:wood*. +### Item model - `model-name` + +Name of the item model. The model will be loaded automatically. +Default value is `packid:itemname.model`. +If the model is not specified, an automatic one will be generated. + ## Behaviour ### *placing-block* diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 2ed46bdb..d1fdc645 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -21,6 +21,7 @@ Subsections: - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [time](scripting/builtins/libtime.md) + - [utf8](scripting/builtins/libutf8.md) - [vec2, vec3, vec4](scripting/builtins/libvecn.md) - [world](scripting/builtins/libworld.md) - [Module core:bit_converter](scripting/modules/core_bit_converter.md) diff --git a/doc/en/scripting/builtins/libentities.md b/doc/en/scripting/builtins/libentities.md index c0a5d260..cb46a3a5 100644 --- a/doc/en/scripting/builtins/libentities.md +++ b/doc/en/scripting/builtins/libentities.md @@ -23,6 +23,9 @@ entities.exists(uid: int) -> bool -- Returns entity definition index by UID entities.get_def(uid: int) -> int +-- Returns entity 'hitbox' property value +entities.def_hitbox(id: int) -> vec3 + -- Returns entity definition name by index (string ID). entities.def_name(id: int) -> str diff --git a/doc/en/scripting/builtins/libitem.md b/doc/en/scripting/builtins/libitem.md index 2b695202..491fd620 100644 --- a/doc/en/scripting/builtins/libitem.md +++ b/doc/en/scripting/builtins/libitem.md @@ -18,4 +18,13 @@ item.defs_count() -> int -- Returns item icon name to use in 'src' property of an image element item.icon(itemid: int) -> str + +-- Returns the integer id 'placing-block' or 0 +item.placing_block(itemid: int) -> int + +-- Returns the value of the `model-name` property +item.model_name(itemid: int) -> str + +-- Returns item emission property value +item.emission(itemid: int) -> str ``` diff --git a/doc/en/scripting/builtins/libutf8.md b/doc/en/scripting/builtins/libutf8.md new file mode 100644 index 00000000..db8b31c5 --- /dev/null +++ b/doc/en/scripting/builtins/libutf8.md @@ -0,0 +1,27 @@ +# *utf8* library + +The library provides functions for working with UTF-8. + +```lua +-- Converts a UTF-8 string to a Bytearray or an array of numbers if +-- the second argument is true +utf8.tobytes(text: str, [optional] usetable=false) -> Bytearray|table + +-- Converts a Bytearray or an array of numbers to a UTF-8 string +utf8.tostring(bytes: Bytearray|table) -> str + +-- Returns the length of a Unicode string +utf8.length(text: str) -> int + +-- Returns the code of the first character of the string +utf8.codepoint(chars: str) -> int + +-- Returns a substring from position startchar to endchar inclusive +utf8.sub(text: str, startchar: int, [optional] endchar: int) -> str + +-- Converts a string to uppercase +utf8.upper(text: str) -> str + +-- Converts a string to lowercase +utf8.lower(text: str) -> str +``` diff --git a/doc/en/scripting/builtins/libworld.md b/doc/en/scripting/builtins/libworld.md index 143fded1..afd8c240 100644 --- a/doc/en/scripting/builtins/libworld.md +++ b/doc/en/scripting/builtins/libworld.md @@ -1,6 +1,7 @@ # *world* library ```lua +-- Returns worlds information. world.get_list() -> tables array { -- world name name: str, @@ -27,6 +28,9 @@ world.get_total_time() -> number -- Returns world seed. world.get_seed() -> int +-- Returns generator name. +world.get_generator() -> str + -- Proves that this is the current time during the day -- from 0.333(8 am) to 0.833(8 pm). world.is_day() -> boolean diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 45b8969b..10b9e3f1 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -136,3 +136,30 @@ function on_hud_close(playerid: int) ``` Called on world close (before saving) + +## *events* library + +```lua +events.on(code: str, handler: function) +``` + +Adds an event handler by its code, not limited to the standard ones. + +```lua +events.reset(code: str, [optional] handler: function) +``` + +Removes the event, adding a handler if specified. + +```lua +events.emit(code: str, args...) -> bool +``` + +Emits an event by code. If the event does not exist, nothing will happen. +The existence of an event is determined by the presence of handlers. + +```lua +events.remove_by_prefix(packid: str) +``` + +Removes all events with the prefix `packid:`. When you exit the world, events from all packs are unloaded, including `core:`. diff --git a/doc/en/scripting/user-input.md b/doc/en/scripting/user-input.md index 07070750..402f9e67 100644 --- a/doc/en/scripting/user-input.md +++ b/doc/en/scripting/user-input.md @@ -66,6 +66,18 @@ input.get_binding_text(bindname: str) -> str Returns text representation of button by binding name. +```python +input.is_active(bindname: str) -> bool +``` + +Checks if the binding is active. + +```python +input.set_enabled(bindname: str, flag: bool) +``` + +Enables/disables binding until leaving the world. + ```python input.is_pressed(code: str) -> bool ``` diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index 0de466bb..47f46b80 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -72,15 +72,13 @@ Fragments used by the generator must present in the directory: ## Structures -A structure is a set of rules for inserting a fragment into the world by the generator. It currently has no properties, being created as empty objects in the `generators/generator_name.files/structures.json` file. Example: -```lua -{ - "tree0": {}, - "tree1": {}, - "tree2": {}, - "tower": {}, - "coal_ore0": {} -} +A structure is a set of rules for inserting a fragment into the world by the generator. It currently has no properties, being created as empty objects in the `generators/generator_name.files/structures.toml` file. Example: +```toml +tree0 = {} +tree1 = {} +tree2 = {} +tower = {} +coal_ore0 = {} ``` Currently, the name of the structure must match the name of the fragment used. @@ -136,7 +134,7 @@ structures = [ - block - plant block - structure-chance - probability of generating a small structure on a surface block. - structures - structures randomly placed on the surface. - - name - name of the structure declared in `structures.json`. + - name - name of the structure declared in `structures.toml`. - weight - weight directly affecting the chance of choosing a specific structure. ### Biome Parameters @@ -301,6 +299,7 @@ Changes the heightmap size. Available interpolation modes: - 'nearest' - no interpolation - 'linear' - bilinear interpolation +- 'cubic' - bicubic interpolation ### heightmap:crop(...) @@ -352,7 +351,15 @@ generation.save_fragment( The fragment size is available as the `size` property. -A fragment can be cropped to fit its contents (air is ignored) by calling the `fragment:crop()` method. +### Methods + +```lua +-- Crop a fragment to content +fragment:crop() + +-- Set a fragment to the world at the specified position +fragment:place(position: vec3, [optional] rotation:int=0) +``` ## Generating a height map diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 61451b80..ac657e44 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -99,6 +99,7 @@ Inner text - initially entered text - `placeholder` - placeholder text (used if the text field is empty) - `supplier` - text supplier (called every frame) - `consumer` - lua function that receives the entered text. Called only when input is complete +- `sub-consumer` - lua function-receiver of the input text. Called during text input or deletion. - `autoresize` - automatic change of element size (default - false). Does not affect font size. - `multiline` - allows display of multiline text. - `text-wrap` - allows automatic text wrapping (works only with multiline: "true") diff --git a/doc/ru/entity-properties.md b/doc/ru/entity-properties.md index 3a6825ac..ec11306c 100644 --- a/doc/ru/entity-properties.md +++ b/doc/ru/entity-properties.md @@ -99,4 +99,4 @@ | save-skeleton-pose | поза скелета сущности | false | | save-skeleton-textures | динамически назначенные текстуры | false | | save-body-velocity | скорость движения тела | true | -| save-body-settings | измененные настройки тела
(type, damping, crouching) | false | +| save-body-settings | измененные настройки тела
(type, damping, crouching) | true | diff --git a/doc/ru/item-properties.md b/doc/ru/item-properties.md index 50f82f2c..6251278f 100644 --- a/doc/ru/item-properties.md +++ b/doc/ru/item-properties.md @@ -11,6 +11,12 @@ - items (генерируется из png файлов в `res/textures/items/`) - `block` - отображает предпросмотр блока. В icon указывается строковый id блока который нужно отображать. Пример `base:wood` +### Модель предмета - `model-name` + +Имя модели предмета. Модель будет загружена автоматически. +Значение по-умолчанию - `packid:itemname.model`. +Если модель не указана, будет сгенерирована автоматическию + ## Поведение ### Устанавливаемый блок - `placing-block` diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index 1f582d46..0900788e 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -21,6 +21,7 @@ - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [time](scripting/builtins/libtime.md) + - [utf8](scripting/builtins/libutf8.md) - [vec2, vec3, vec4](scripting/builtins/libvecn.md) - [world](scripting/builtins/libworld.md) - [Модуль core:bit_converter](scripting/modules/core_bit_converter.md) diff --git a/doc/ru/scripting/builtins/libentities.md b/doc/ru/scripting/builtins/libentities.md index 93275e5b..1e0cc6c0 100644 --- a/doc/ru/scripting/builtins/libentities.md +++ b/doc/ru/scripting/builtins/libentities.md @@ -26,6 +26,9 @@ entities.get_def(uid: int) -> int -- Возвращает имя определения сущности по индексу (строковый ID). entities.def_name(id: int) -> str +-- Возвращает значение свойства 'hitbox' сущности +entities.def_hitbox(id: int) -> vec3 + -- Возвращает индекс определения сущности по имени (числовой ID). entities.def_index(name: str) -> int diff --git a/doc/ru/scripting/builtins/libitem.md b/doc/ru/scripting/builtins/libitem.md index 0bbfcd2a..542247b8 100644 --- a/doc/ru/scripting/builtins/libitem.md +++ b/doc/ru/scripting/builtins/libitem.md @@ -8,7 +8,7 @@ item.name(itemid: int) -> str item.index(name: str) -> int -- Возвращает название предмета, отображаемое в интерфейсе. -item.caption(blockid: int) -> str +item.caption(itemid: int) -> str -- Возвращает максимальный размер стопки для предмета. item.stack_size(itemid: int) -> int @@ -18,6 +18,15 @@ item.defs_count() -> int -- Возвращает имя иконки предмета для использования в свойстве 'src' элемента image item.icon(itemid: int) -> str + +-- Возвращает числовой id блока, назначенного как 'placing-block' или 0 +item.placing_block(itemid: int) -> int + +-- Возвращает значение свойства `model-name` +item.model_name(itemid: int) -> str + +-- Возвращает emission параметр у предмета +item.emission(itemid: int) -> str ``` diff --git a/doc/ru/scripting/builtins/libpack.md b/doc/ru/scripting/builtins/libpack.md index 0beb0667..543ea7a3 100644 --- a/doc/ru/scripting/builtins/libpack.md +++ b/doc/ru/scripting/builtins/libpack.md @@ -31,7 +31,7 @@ file.write(pack.shared_file(PACK_ID, "example.txt"), text) ``` Для пака *containermod* запишет текст в файл `config:containermod/example.txt` -Используйте для хранения данныхm общих для всех миров. +Используйте для хранения данных общих для всех миров. ```python pack.get_folder(packid: str) -> str diff --git a/doc/ru/scripting/builtins/libutf8.md b/doc/ru/scripting/builtins/libutf8.md new file mode 100644 index 00000000..50d64e72 --- /dev/null +++ b/doc/ru/scripting/builtins/libutf8.md @@ -0,0 +1,27 @@ +# Библиотека *utf8* + +Библиотека предоставляет функции для работы с UTF-8. + +```lua +-- Конвертирует UTF-8 строку в Bytearray или массив чисел если +-- второй аргумент - true +utf8.tobytes(text: str, [опционально] usetable=false) -> Bytearray|table + +-- Конвертирует Bytearray или массив чисел в UTF-8 строку +utf8.tostring(bytes: Bytearray|table) -> str + +-- Возвращает длину юникод-строки +utf8.length(text: str) -> int + +-- Возвращает код первого символа строки +utf8.codepoint(chars: str) -> int + +-- Возвращает подстроку от позиции startchar до endchar включительно +utf8.sub(text: str, startchar: int, [опционально] endchar: int) -> str + +-- Переводит строку в вверхний регистр +utf8.upper(text: str) -> str + +-- Переводит строку в нижний регистр +utf8.lower(text: str) -> str +``` diff --git a/doc/ru/scripting/builtins/libworld.md b/doc/ru/scripting/builtins/libworld.md index 2c95e578..31806de4 100644 --- a/doc/ru/scripting/builtins/libworld.md +++ b/doc/ru/scripting/builtins/libworld.md @@ -27,6 +27,9 @@ world.get_total_time() -> number -- Возвращает зерно мира. world.get_seed() -> int +-- Возвращает имя генератора. +world.get_generator() -> str + -- Проверяет существование мира по имени. world.exists() -> bool diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index f3a3045b..d7805d60 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -135,3 +135,30 @@ function on_hud_close(playerid: int) ``` Вызывается при выходе из мира, перед его сохранением. + +## Библиотека *events* + +```lua +events.on(code: str, handler: function) +``` + +Добавляет обработчик события по его коду, не ограничиваясь стандартными. + +```lua +events.reset(code: str, [опционально] handler: function) +``` + +Удаляет событие, добавляя обработчик, если указан. + +```lua +events.emit(code: str, args...) -> bool +``` + +Генерирует событие по коду. Если событие не существует, ничего не произойдет. +Существование события определяется наличием обработчиков. + +```lua +events.remove_by_prefix(packid: str) +``` + +Удаляет все события с префиксом `packid:`. Вы выходе из мира выгружаются события всех паков, включая `core:`. diff --git a/doc/ru/scripting/user-input.md b/doc/ru/scripting/user-input.md index 2b0c9f5c..686d962b 100644 --- a/doc/ru/scripting/user-input.md +++ b/doc/ru/scripting/user-input.md @@ -70,6 +70,12 @@ input.is_active(bindname: str) -> bool Проверяет активность привязки. +```python +input.set_enabled(bindname: str, flag: bool) +``` + +Включает/выключает привязку до выхода из мира. + ```python input.is_pressed(code: str) -> bool ``` diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index ec3112cb..284ea067 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -72,15 +72,13 @@ ## Структуры -Структура - набор правил по вставке фрагмента в мир генератором. На данный момент не имеет свойств, создаваясь в виде пустых объектов в файле `generators/имя_генератора.files/structures.json`. Пример: -```lua -{ - "tree0": {}, - "tree1": {}, - "tree2": {}, - "tower": {}, - "coal_ore0": {} -} +Структура - набор правил по вставке фрагмента в мир генератором. На данный момент не имеет свойств, создаваясь в виде пустых объектов в файле `generators/имя_генератора.files/structures.toml`. Пример: +```toml +tree0 = {} +tree1 = {} +tree2 = {} +tower = {} +coal_ore0 = {} ``` На данный момент, имя структуры должно совпадать с именем использованного фрагмента. @@ -136,7 +134,7 @@ structures = [ - block - блок растения - structure-chance - вероятность генерации малой структуры на блоке поверхности. - structures - структуры, случайно расставляемые на поверхности. - - name - имя структуры, объявленной в `structures.json`. + - name - имя структуры, объявленной в `structures.toml`. - weight - вес, напрямую влияющий на шанс выбора конкретной структуры. ### Параметры биомов @@ -305,6 +303,7 @@ map:resize(ширина, высота, интерполяция) Доступные режимы интерполяции: - 'nearest' - без интерполяции - 'linear' - билинейная интерполяция +- 'cubic' - бикубическая интерполяция ### heightmap:crop(...) @@ -356,7 +355,15 @@ generation.save_fragment( Размер фрагмента доступен как свойство `size`. -Фрагмент может быть обрезан до размеров содержимого (воздух игнорируется) вызовом метода `fragment:crop()`. +### Методы + +```lua +-- Обрезает фрагмент до размеров содержимого +fragment:crop() + +-- Устанавливает фрагмент в мир на указанной позиции +fragment:place(position: vec3, [опционально] rotation:int=0) +``` ## Генерация карты высот diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index 16007a2e..bdf82f1b 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -100,6 +100,7 @@ - `placeholder` - текст подстановки (используется если текстовое поле пусто) - `supplier` - поставщик текста (вызывается каждый кадр) - `consumer` - lua функция-приемник введенного текста. Вызывается только при завершении ввода +- `sub-consumer` - lua функция-приемник вводимого текста. Вызывается во время ввода или удаления текста. - `autoresize` - автоматическое изменение размера элемента (по-умолчанию - false). Не влияет на размер шрифта. - `multiline` - разрешает отображение многострочного текста. - `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true") diff --git a/doc/specs/vec3_model_spec.md b/doc/specs/vec3_model_spec.md new file mode 100644 index 00000000..b9c2868c --- /dev/null +++ b/doc/specs/vec3_model_spec.md @@ -0,0 +1,109 @@ +# VEC3 format specification + +3D models binary format. + +Byteorder: little-endian + +## Syntax + +```cpp +enum AttributeType:uint8 { + POSITION = 0, + UV, + NORMAL, + COLOR, +}; +sizeof(AttributeType) == 1; + +struct VertexAttribute { + AttributeType type; // data type is infered from attribute type + uint8 flags; + uint32 size; + float data[]; // if compressed, first 4 bytes of compressed data is decompressed size +}; +sizeof(VertexAttribute) == 6; // + dynamic data array + +struct Mesh { + uint32 triangle_count; // number of mesh triangles + uint16 material_id; + uint16 flags; + uint16 attribute_count; + VertexAttribute attributes[]; + uint8 indices[]; // if compressed, first 4 bytes of compressed data is compressed buffer size +}; +sizeof(Mesh) == 10; // + dynamic attributes array + dynamic indices array + +struct Model { + uint16 name_len; + vec3 origin; + uint32 mesh_count; + Mesh meshes[]; + char name[]; +}; +sizeof(Model) == 18; // + dynamic Mesh array + name length + +struct Material { + uint16 flags; + uint16 name_len; + char name[]; +}; +sizeof(Material) == 4; // + dynamic sized string + +struct Header { + char[8] ident; // "\0\0VEC3\0\0" + uint16 version; // current is 1 + uint16 reserved; // 0x0000 +}; +sizeof(Header) == 12; + +struct Body { + uint16 material_count + uint16 model_count + Material materials[]; + Model models[]; +}; +sizeof(Body) == 4; // + dynamic models array + dynamic materials array + +``` + +\* vertex data: positions are global. Model origins used to make it local. + +vertex - is a set of vertex data section entries indices divided by stride, starting from 0 (section first entry). + +Example: in file having sections (coordinates, texture_coordinates, normal) vertex is a set of 3 indices ordered the +same way as sections stored in the file. + +## Vertex Data section tags + +All sections are optional. + +| Value | Name | Stride (bytes) | Description | +| ----- | ------------------- | -------------- | --------------------------- | +| %x01 | Coordinates | 12 | vertex position | +| %x02 | Texture coordinates | 8 | vertex texture coordinates | +| %x03 | Normals | 12 | vertex normal vector | +| %x04 | Color | 16 | vertex RGBA color (0.0-1.0) | + +VertexAttribute flags: + +| Value | Name | +| ----- | ---------------- | +| %x01 | ZLib compression | + +## Mesh + +Mesh flags: + +| Value | Name | +| ----- | ----------------------------------- | +| %x01 | Indices ZLib compression | +| %x02 | Use 16 bit indices instead of 8 bit | + +## Material + +Material flags: + +| Bit offset | Description | +|------------|-------------| +| 0 | Shadeless | +| 1-7 | Reserved | diff --git a/res/config/bindings.toml b/res/config/bindings.toml index 2d373cb9..a0ab278f 100644 --- a/res/config/bindings.toml +++ b/res/config/bindings.toml @@ -16,4 +16,5 @@ player.attack="mouse:left" player.build="mouse:right" player.pick="mouse:middle" player.drop="key:q" +player.fast_interaction="key:x" hud.inventory="key:tab" diff --git a/res/content/base/blocks/water.json b/res/content/base/blocks/water.json index de702de1..943640e8 100644 --- a/res/content/base/blocks/water.json +++ b/res/content/base/blocks/water.json @@ -1,9 +1,10 @@ { "texture": "water", + "overlay-texture": "blocks:water", "draw-group": 3, "light-passing": true, "sky-light-passing": false, "obstacle": false, "selectable": false, "replaceable": true -} \ No newline at end of file +} diff --git a/res/content/base/models/block.obj b/res/content/base/models/block.obj deleted file mode 100644 index 2e307e52..00000000 --- a/res/content/base/models/block.obj +++ /dev/null @@ -1,48 +0,0 @@ -o Cube -v 0.5 -0.5 -0.5 -v 0.5 -0.5 0.5 -v -0.5 -0.5 0.5 -v -0.5 -0.5 -0.5 -v 0.5 0.5 -0.5 -v 0.5 0.5 0.5 -v -0.5 0.5 0.5 -v -0.5 0.5 -0.5 -vt 0.0 0.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 0.0 1.0 -vt 0.0 0.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 0.0 1.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 0.0 0.0 -vt 1.0 0.0 -vt 0.0 1.0 -vt 0.0 0.0 -vt 0.0 1.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 1.0 1.0 -vt 0.0 1.0 -vt 0.0 0.0 -vn 0.0 -1.0 0.0 -vn 0.0 1.0 0.0 -vn 1.0 -0.0 0.0 -vn -1.0 -0.0 -0.0 -vn 0.0 0.0 -1.0 -vn -0.0 -0.0 1.0 -usemtl $2 -s off -f 1/1/1 2/2/1 3/3/1 4/4/1 -usemtl $3 -f 5/5/2 8/6/2 7/7/2 6/8/2 -usemtl $0 -f 1/9/3 5/10/3 6/8/3 2/11/3 -usemtl $1 -f 3/12/4 7/7/4 8/13/4 4/14/4 -usemtl $4 -f 5/15/5 1/1/5 4/16/5 8/17/5 -usemtl $5 -f 2/2/6 6/18/6 7/19/6 3/20/6 diff --git a/res/content/base/models/drop-block.obj b/res/content/base/models/drop-block.obj deleted file mode 100644 index be4cf551..00000000 --- a/res/content/base/models/drop-block.obj +++ /dev/null @@ -1,48 +0,0 @@ -o Cube -v 0.125 -0.125 -0.125 -v 0.125 -0.125 0.125 -v -0.125 -0.125 0.125 -v -0.125 -0.125 -0.125 -v 0.125 0.125 -0.125 -v 0.125 0.125 0.125 -v -0.125 0.125 0.125 -v -0.125 0.125 -0.125 -vt 0.0 0.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 0.0 1.0 -vt 0.0 0.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 0.0 1.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 0.0 0.0 -vt 1.0 0.0 -vt 0.0 1.0 -vt 0.0 0.0 -vt 0.0 1.0 -vt 1.0 0.0 -vt 1.0 1.0 -vt 1.0 1.0 -vt 0.0 1.0 -vt 0.0 0.0 -vn 0.0 -1.0 0.0 -vn 0.0 1.0 0.0 -vn 1.0 -0.0 0.0 -vn -1.0 -0.0 -0.0 -vn 0.0 0.0 -1.0 -vn -0.0 -0.0 1.0 -usemtl $2 -s off -f 1/1/1 2/2/1 3/3/1 4/4/1 -usemtl $3 -f 5/5/2 8/6/2 7/7/2 6/8/2 -usemtl $0 -f 1/9/3 5/10/3 6/8/3 2/11/3 -usemtl $1 -f 3/12/4 7/7/4 8/13/4 4/14/4 -usemtl $4 -f 5/15/5 1/1/5 4/16/5 8/17/5 -usemtl $5 -f 2/2/6 6/18/6 7/19/6 3/20/6 diff --git a/res/content/base/models/drop-block.vec3 b/res/content/base/models/drop-block.vec3 new file mode 100644 index 00000000..b9c3a10c Binary files /dev/null and b/res/content/base/models/drop-block.vec3 differ diff --git a/res/content/base/models/drop-item.obj b/res/content/base/models/drop-item.obj deleted file mode 100644 index a12cca01..00000000 --- a/res/content/base/models/drop-item.obj +++ /dev/null @@ -1,113 +0,0 @@ -o Cube -v 0.282501 -0.000054 -0.282500 -v -0.282501 -0.000054 -0.282501 -v -0.282501 -0.000054 0.282500 -v 0.282500 -0.000054 0.282501 -v 0.282501 0.012502 -0.282500 -v -0.282501 0.012502 -0.282501 -v -0.282501 0.012502 0.282500 -v 0.282500 0.012502 0.282501 -v 0.282501 0.012502 -0.282500 -v 0.282500 0.012502 0.282501 -v -0.282501 0.012502 0.282500 -v -0.282501 0.012502 -0.282501 -v 0.282501 -0.000054 -0.282500 -v 0.282500 -0.000054 0.282501 -v -0.282501 -0.000054 0.282500 -v -0.282501 -0.000054 -0.282501 -v 0.282501 0.012502 -0.282500 -v -0.282501 0.012502 -0.282501 -v -0.282501 0.012502 0.282500 -v 0.282500 0.012502 0.282501 -v 0.282501 0.012502 -0.282500 -v 0.282500 0.012502 0.282501 -v -0.282501 0.012502 0.282500 -v -0.282501 0.012502 -0.282501 -v 0.282501 -0.015821 -0.282500 -v -0.282501 -0.015821 -0.282501 -v -0.282501 -0.015821 0.282500 -v 0.282500 -0.015821 0.282501 -v 0.282501 0.027439 -0.282500 -v -0.282501 0.027439 -0.282501 -v -0.282501 0.027439 0.282500 -v 0.282500 0.027439 0.282501 -v 0.282501 0.027439 -0.282500 -v 0.282500 0.027439 0.282501 -v -0.282501 0.027439 0.282500 -v -0.282501 0.027439 -0.282501 -v 0.282501 -0.015821 -0.282500 -v 0.282500 -0.015821 0.282501 -v -0.282501 -0.015821 0.282500 -v -0.282501 -0.015821 -0.282501 -v 0.282501 0.027439 -0.282500 -v -0.282501 0.027439 -0.282501 -v -0.282501 0.027439 0.282500 -v 0.282500 0.027439 0.282501 -v 0.282501 0.027439 -0.282500 -v 0.282500 0.027439 0.282501 -v -0.282501 0.027439 0.282500 -v -0.282501 0.027439 -0.282501 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 1.000000 0.000000 -vt 0.000000 0.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 1.000000 0.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 1.000000 0.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 1.000000 0.000000 -vt 0.000000 0.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 1.000000 0.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 1.000000 0.000000 -vn -0.0000 1.0000 0.0000 -vn 0.0000 -1.0000 -0.0000 -usemtl $0 -s 1 -f 1/1/1 2/2/1 3/3/1 4/4/1 -f 5/5/1 6/6/1 7/7/1 8/8/1 -f 9/9/2 10/10/2 11/11/2 12/12/2 -f 13/13/2 14/14/2 15/15/2 16/16/2 -f 17/17/1 18/18/1 19/19/1 20/20/1 -f 21/21/2 22/22/2 23/23/2 24/24/2 -f 25/25/1 26/26/1 27/27/1 28/28/1 -f 29/29/1 30/30/1 31/31/1 32/32/1 -f 33/33/2 34/34/2 35/35/2 36/36/2 -f 37/37/2 38/38/2 39/39/2 40/40/2 -f 41/41/1 42/42/1 43/43/1 44/44/1 -f 45/45/2 46/46/2 47/47/2 48/48/2 diff --git a/res/content/base/models/player-body.obj b/res/content/base/models/player-body.obj deleted file mode 100644 index 4cfbad54..00000000 --- a/res/content/base/models/player-body.obj +++ /dev/null @@ -1,47 +0,0 @@ -# Blender v2.79 (sub 0) OBJ File: 'player.blend' -# www.blender.org -mtllib player-body.mtl -o Cube.001 -v -0.125000 -0.900000 0.070903 -v -0.125000 -0.900000 -0.070903 -v 0.125000 -0.900000 -0.070903 -v 0.125000 -0.900000 0.070903 -v -0.125000 0.491919 0.070903 -v 0.125000 0.491919 0.070903 -v 0.125000 0.491919 -0.070903 -v -0.125000 0.491919 -0.070903 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.112175 0.434439 -vt 0.311556 0.434439 -vt 0.311556 0.633819 -vt 0.112175 0.633819 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vn 0.0000 -1.0000 0.0000 -vn 0.0000 1.0000 0.0000 -vn -1.0000 0.0000 0.0000 -vn -0.0000 -0.0000 -1.0000 -vn 1.0000 0.0000 0.0000 -vn 0.0000 0.0000 1.0000 -usemtl entities/player -s 1 -f 1/1/1 2/2/1 3/3/1 4/4/1 -f 5/5/2 6/6/2 7/7/2 8/8/2 -f 1/1/3 5/9/3 8/10/3 2/11/3 -f 2/12/4 8/13/4 7/14/4 3/15/4 -f 3/16/5 7/17/5 6/18/5 4/4/5 -f 5/5/6 1/19/6 4/20/6 6/21/6 diff --git a/res/content/base/models/player-body.vec3 b/res/content/base/models/player-body.vec3 new file mode 100644 index 00000000..8734c9c7 Binary files /dev/null and b/res/content/base/models/player-body.vec3 differ diff --git a/res/content/base/models/player-hand.obj b/res/content/base/models/player-hand.obj deleted file mode 100644 index a7752299..00000000 --- a/res/content/base/models/player-hand.obj +++ /dev/null @@ -1,42 +0,0 @@ -# Blender v2.79 (sub 0) OBJ File: 'player.blend' -# www.blender.org -mtllib player-hand.mtl -o Cube.000_Cube.002 -v 0.062480 -0.613786 -0.062480 -v 0.062480 -0.613786 0.062480 -v -0.062480 -0.613786 0.062480 -v -0.062480 -0.613786 -0.062480 -v 0.062480 0.070352 -0.062480 -v -0.062480 0.070352 -0.062480 -v -0.062480 0.070352 0.062480 -v 0.062480 0.070352 0.062480 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.436482 0.280393 -vt 0.433740 0.937829 -vt 0.436146 0.914519 -vt 0.438665 0.292591 -vt 0.492515 0.918221 -vt 0.493194 0.293103 -vt 0.493371 0.941872 -vt 0.494058 0.280870 -vn 0.0000 -1.0000 0.0000 -vn 0.0000 1.0000 -0.0000 -vn 1.0000 -0.0000 0.0000 -vn -0.0000 -0.0000 1.0000 -vn -1.0000 0.0000 0.0000 -vn 0.0000 0.0000 -1.0000 -usemtl entities/player -s 1 -f 1/1/1 2/2/1 3/3/1 4/4/1 -f 5/5/2 6/6/2 7/7/2 8/8/2 -f 1/9/3 5/10/3 8/11/3 2/12/3 -f 2/12/4 8/11/4 7/13/4 3/14/4 -f 3/14/5 7/13/5 6/15/5 4/16/5 -f 5/10/6 1/9/6 4/16/6 6/15/6 diff --git a/res/content/base/models/player-hand.vec3 b/res/content/base/models/player-hand.vec3 new file mode 100644 index 00000000..c134ce94 Binary files /dev/null and b/res/content/base/models/player-hand.vec3 differ diff --git a/res/content/base/models/player-head.obj b/res/content/base/models/player-head.obj deleted file mode 100644 index c8baf870..00000000 --- a/res/content/base/models/player-head.obj +++ /dev/null @@ -1,48 +0,0 @@ -# Blender v2.79 (sub 0) OBJ File: 'player.blend' -# www.blender.org -mtllib player-head.mtl -o Cube.002_Cube.003 -v -0.206512 0.031837 0.206512 -v -0.206512 0.444861 0.206512 -v -0.206512 0.444861 -0.206512 -v -0.206512 0.031837 -0.206512 -v 0.206512 0.444861 -0.206512 -v 0.206512 0.031837 -0.206512 -v 0.206512 0.444861 0.206512 -v 0.206512 0.031837 0.206512 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.735873 0.213345 -vt 0.735873 0.739780 -vt 0.209439 0.739780 -vt 0.209439 0.213345 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.209065 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.982503 0.209065 -vt 0.783122 0.009685 -vt 0.982503 0.009685 -vt 0.783122 0.209065 -vn -1.0000 -0.0000 -0.0000 -vn 0.0000 0.0000 -1.0000 -vn 1.0000 0.0000 0.0000 -vn -0.0000 -0.0000 1.0000 -vn 0.0000 -1.0000 -0.0000 -vn -0.0000 1.0000 0.0000 -usemtl entities/player -s 1 -f 1/1/1 2/2/1 3/3/1 4/4/1 -f 4/5/2 3/6/2 5/7/2 6/8/2 -f 6/9/3 5/10/3 7/11/3 8/12/3 -f 8/13/4 7/14/4 2/15/4 1/16/4 -f 4/17/5 6/18/5 8/19/5 1/16/5 -f 5/20/6 3/21/6 2/15/6 7/22/6 diff --git a/res/content/base/models/player-head.vec3 b/res/content/base/models/player-head.vec3 new file mode 100644 index 00000000..71f47c10 Binary files /dev/null and b/res/content/base/models/player-head.vec3 differ diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 90e4810f..195c5270 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -1,5 +1,3 @@ -local item_models = require "core:item_models" - local tsf = entity.transform local body = entity.rigidbody local rig = entity.skeleton @@ -27,7 +25,7 @@ end do -- setup visuals local matrix = mat4.idt() - scale = item_models.setup(dropitem.id, rig, 0) + rig:set_model(0, item.model_name(dropitem.id)) local bodysize = math.min(scale[1], scale[2], scale[3]) * DROP_SCALE body:set_size({scale[1] * DROP_SCALE, bodysize, scale[3] * DROP_SCALE}) mat4.mul(matrix, rotation, matrix) @@ -38,9 +36,7 @@ end function on_grounded(force) local matrix = mat4.idt() mat4.rotate(matrix, {0, 1, 0}, math.random()*360, matrix) - if model == "aabb" then - mat4.rotate(matrix, {1, 0, 0}, 90, matrix) - end + mat4.rotate(matrix, {1, 0, 0}, 90, matrix) mat4.scale(matrix, scale, matrix) rig:set_matrix(0, matrix) inair = false diff --git a/res/content/base/scripts/components/player_animator.lua b/res/content/base/scripts/components/player_animator.lua index d1a4c0be..871284d5 100644 --- a/res/content/base/scripts/components/player_animator.lua +++ b/res/content/base/scripts/components/player_animator.lua @@ -1,5 +1,3 @@ -local item_models = require "core:item_models" - local tsf = entity.transform local body = entity.rigidbody local rig = entity.skeleton @@ -9,12 +7,8 @@ local itemIndex = rig:index("item") local function refresh_model(id) itemid = id - if id == 0 then - rig:set_model(itemIndex, "") - else - local scale = item_models.setup(itemid, rig, itemIndex) - rig:set_matrix(itemIndex, mat4.scale(scale)) - end + rig:set_model(itemIndex, item.model_name(itemid)) + rig:set_matrix(itemIndex, mat4.rotate({0, 1, 0}, -80)) end function on_render() diff --git a/res/content/base/textures/blocks/water.png b/res/content/base/textures/blocks/water.png index 00dac81b..e06e127d 100644 Binary files a/res/content/base/textures/blocks/water.png and b/res/content/base/textures/blocks/water.png differ diff --git a/res/layouts/console.xml b/res/layouts/console.xml index bbe869d0..3e1b802d 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -11,6 +11,13 @@ gravity="bottom-left" > + + + = #history-1 then return end history_pointer = history_pointer + 1 diff --git a/res/layouts/pages/content_menu.xml b/res/layouts/pages/content_menu.xml index aa9f7dd9..097e068e 100644 --- a/res/layouts/pages/content_menu.xml +++ b/res/layouts/pages/content_menu.xml @@ -3,6 +3,7 @@ + diff --git a/res/layouts/pages/settings.xml b/res/layouts/pages/settings.xml index 223a7f96..f5ec33d7 100644 --- a/res/layouts/pages/settings.xml +++ b/res/layouts/pages/settings.xml @@ -10,6 +10,8 @@ + + diff --git a/res/layouts/pages/settings.xml.lua b/res/layouts/pages/settings.xml.lua index 345d5620..24256c84 100644 --- a/res/layouts/pages/settings.xml.lua +++ b/res/layouts/pages/settings.xml.lua @@ -11,6 +11,7 @@ function set_page(btn, page) document.s_dsp.enabled = true document.s_gfx.enabled = true document.s_ctl.enabled = true + document.s_rst.enabled = true document[btn].enabled = false document.menu.page = page end diff --git a/res/layouts/pages/settings_controls.xml b/res/layouts/pages/settings_controls.xml index 678b129f..30927037 100644 --- a/res/layouts/pages/settings_controls.xml +++ b/res/layouts/pages/settings_controls.xml @@ -5,7 +5,7 @@ consumer='change_sensitivity'> - + diff --git a/res/layouts/pages/settings_display.xml.lua b/res/layouts/pages/settings_display.xml.lua index 492ddef9..f8b24d0b 100644 --- a/res/layouts/pages/settings_display.xml.lua +++ b/res/layouts/pages/settings_display.xml.lua @@ -59,4 +59,6 @@ function on_open() create_checkbox("display.fullscreen", "Fullscreen") create_checkbox("camera.shaking", "Camera Shaking") create_checkbox("camera.inertia", "Camera Inertia") + create_checkbox("camera.fov-effects", "Camera FOV Effects") + create_checkbox("display.limit-fps-iconified", "Limit Background FPS") end diff --git a/res/layouts/pages/settings_reset.xml b/res/layouts/pages/settings_reset.xml new file mode 100644 index 00000000..e7bbc83a --- /dev/null +++ b/res/layouts/pages/settings_reset.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/res/layouts/pages/settings_reset.xml.lua b/res/layouts/pages/settings_reset.xml.lua new file mode 100644 index 00000000..a8ca2a9f --- /dev/null +++ b/res/layouts/pages/settings_reset.xml.lua @@ -0,0 +1,44 @@ +function reset(category) + if category == "aud" then + reset_audio() + elseif category == "dsp" then + reset_display() + elseif category == "gfx" then + reset_graphics() + elseif category == "ctl" then + reset_control() + end +end + +function reset_setting(name) + core.set_setting(name, core.get_setting_info(name).def) +end + +function reset_audio() + reset_setting("audio.volume-master") + reset_setting("audio.volume-regular") + reset_setting("audio.volume-ui") + reset_setting("audio.volume-ambient") + reset_setting("audio.volume-music") +end + +function reset_display() + reset_setting("camera.fov") + reset_setting("display.framerate") + reset_setting("display.fullscreen") + reset_setting("camera.shaking") + reset_setting("camera.inertia") + reset_setting("camera.fov-effects") +end + +function reset_graphics() + reset_setting("chunks.load-distance") + reset_setting("chunks.load-speed") + reset_setting("graphics.fog-curve") + reset_setting("graphics.gamma") + reset_setting("graphics.backlight") +end + +function reset_control() + input.reset_bindings() +end diff --git a/res/layouts/templates/problem.xml b/res/layouts/templates/problem.xml new file mode 100644 index 00000000..0dfb2794 --- /dev/null +++ b/res/layouts/templates/problem.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/models/block.vec3 b/res/models/block.vec3 new file mode 100644 index 00000000..0b274e75 Binary files /dev/null and b/res/models/block.vec3 differ diff --git a/res/models/drop-item.vec3 b/res/models/drop-item.vec3 new file mode 100644 index 00000000..d40f7704 Binary files /dev/null and b/res/models/drop-item.vec3 differ diff --git a/res/modules/item_models.lua b/res/modules/item_models.lua deleted file mode 100644 index 6c2c955e..00000000 --- a/res/modules/item_models.lua +++ /dev/null @@ -1,33 +0,0 @@ -local function setup(id, rig, index) - rig:set_model(index, "drop-block") - local icon = item.icon(id) - local size = {1.0, 1.0, 1.0} - if icon:find("^block%-previews%:") then - local bid = block.index(icon:sub(16)) - model = block.get_model(bid) - if model == "X" then - size = {1.0, 0.3, 1.0} - rig:set_model(index, "drop-item") - rig:set_texture("$0", icon) - else - if model == "aabb" then - local rot = block.get_rotation_profile(bid) == "pipe" and 4 or 0 - size = block.get_hitbox(bid, rot)[2] - vec3.mul(size, 2.0, size) - end - local textures = block.get_textures(bid) - for i,t in ipairs(textures) do - rig:set_texture("$"..tostring(i-1), "blocks:"..textures[i]) - end - end - else - size = {1.0, 0.3, 1.0} - rig:set_model(index, "drop-item") - rig:set_texture("$0", icon) - end - return size -end - -return { - setup=setup, -} diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index a2edd9b8..6dec6ca4 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -110,19 +110,39 @@ console.add_command( return "Time set to " .. args[1] end ) + console.add_command( - "blocks.fill id:str x:num~pos.x y:num~pos.y z:num~pos.z w:int h:int d:int", + "time.daycycle operation:[stop|reset]", + "Control time.daycycle", + function(args, kwargs) + local operation = args[1] + if operation == "stop" then + world.set_day_time_speed(0) + return "Daily cycle has stopped" + else + world.set_day_time_speed(1.0) + return "Daily cycle has started" + end + end +) + +console.add_command( + "blocks.fill id:str x1:int~pos.x y1:int~pos.y z1:int~pos.z ".. + "x2:int~pos.x y2:int~pos.y z2:int~pos.z", "Fill specified zone with blocks", function(args, kwargs) - local name, x, y, z, w, h, d = unpack(args) + local name, x1,y1,z1, x2,y2,z2 = unpack(args) local id = block.index(name) - for ly = 0, h - 1 do - for lz = 0, d - 1 do - for lx = 0, w - 1 do - block.set(x + lx, y + ly, z + lz, id) + for y=y1,y2 do + for z=z1,z2 do + for x=x1,x2 do + block.set(x, y, z, id) end end end + local w = math.floor(math.abs(x2-x1+1) + 0.5) + local h = math.floor(math.abs(y2-y1+1) + 0.5) + local d = math.floor(math.abs(z2-z1+1) + 0.5) return tostring(w * h * d) .. " blocks set" end ) @@ -151,22 +171,24 @@ console.add_command( ) console.add_command( - "fragment.save x:int y:int z:int w:int h:int d:int name:str='untitled' crop:bool=false", + "fragment.save x1:int~pos.x y1:int~pos.y z1:int~pos.z ".. + "x2:int~pos.x y2:int~pos.y z2:int~pos.z ".. + "name:str='untitled' crop:bool=false", "Save fragment", function(args, kwargs) - local x = args[1] - local y = args[2] - local z = args[3] + local x1 = args[1] + local y1 = args[2] + local z1 = args[3] - local w = args[4] - local h = args[5] - local d = args[6] + local x2 = args[4] + local y2 = args[5] + local z2 = args[6] local name = args[7] local crop = args[8] local fragment = generation.create_fragment( - {x, y, z}, {x + w, y + h, z + d}, crop, false + {x1, y1, z1}, {x2, y2, z2}, crop, false ) local filename = 'export:'..name..'.vox' generation.save_fragment(fragment, filename, crop) @@ -187,3 +209,17 @@ console.add_command( " has been saved as "..file.resolve(filename)) end ) + +console.add_command( + "fragment.place file:str x:num~pos.x y:num~pos.y z:num~pos.z rotation:int=0", + "Place fragment to the world", + function(args, kwargs) + local filename = args[1] + local x = args[2] + local y = args[3] + local z = args[4] + local rotation = args[5] + local fragment = generation.load_fragment(filename) + fragment:place({x, y, z}, rotation) + end +) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 8fbc779f..8a4f3513 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -9,22 +9,36 @@ function sleep(timesec) end end --- events +------------------------------------------------ +------------------- Events --------------------- +------------------------------------------------ events = { handlers = {} } function events.on(event, func) - -- why an array? length is always = 1 - -- FIXME: temporary fixed - events.handlers[event] = {} -- events.handlers[event] or {} + if events.handlers[event] == nil then + events.handlers[event] = {} + end table.insert(events.handlers[event], func) end +function events.reset(event, func) + if func == nil then + events.handlers[event] = nil + else + events.handlers[event] = {func} + end +end + function events.remove_by_prefix(prefix) for name, handlers in pairs(events.handlers) do - if name:sub(1, #prefix) == prefix then - events.handlers[name] = nil + local actualname = name + if type(name) == 'table' then + actualname = name[1] + end + if actualname:sub(1, #prefix+1) == prefix..':' then + events.handlers[actualname] = nil end end end @@ -34,11 +48,13 @@ function pack.unload(prefix) end function events.emit(event, ...) - result = nil - if events.handlers[event] then - for _, func in ipairs(events.handlers[event]) do - result = result or func(...) - end + local result = nil + local handlers = events.handlers[event] + if handlers == nil then + return nil + end + for _, func in ipairs(handlers) do + result = result or func(...) end return result end diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index e38a39ef..530ec091 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -109,7 +109,8 @@ function string.explode(separator, str, withpattern) local current_pos = 1 for i = 1, string_len(str) do - local start_pos, end_pos = string_find(str, separator, current_pos, not withpattern) + local start_pos, end_pos = string_find( + str, separator, current_pos, not withpattern) if (not start_pos) then break end ret[i] = string_sub(str, current_pos, start_pos - 1) current_pos = end_pos + 1 @@ -139,7 +140,7 @@ function string.formatted_time(seconds, format) end function string.replace(str, tofind, toreplace) - local tbl = string.Explode(tofind, str) + local tbl = string.explode(tofind, str) if (tbl[1]) then return table.concat(tbl, toreplace) end return str end @@ -159,6 +160,9 @@ function string.trim_left(s, char) return string.match(s, "^" .. char .. "*(.+)$") or s end +string.lower = utf8.lower +string.upper = utf8.upper + local meta = getmetatable("") function meta:__index(key) @@ -234,6 +238,7 @@ function on_deprecated_call(name, alternatives) return end __warnings_hidden[name] = true + events.emit("core:warning", "deprecated call", name) if alternatives then debug.warning("deprecated function called ("..name.."), use ".. alternatives.." instead\n"..debug.traceback()) diff --git a/res/shaders/entity.glslf b/res/shaders/entity.glslf index a49bdfa8..b332c02f 100644 --- a/res/shaders/entity.glslf +++ b/res/shaders/entity.glslf @@ -16,7 +16,7 @@ void main() { float depth = (a_distance/256.0); float alpha = a_color.a * tex_color.a; // anyway it's any alpha-test alternative required - if (alpha < 0.3f) + if (alpha < 0.9f) discard; f_color = mix(a_color * tex_color, vec4(fogColor,1.0), min(1.0, pow(depth*u_fogFactor, u_fogCurve))); diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 3f6d1c74..40c684a4 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -31,6 +31,7 @@ hud.inventory=Inventory player.pick=Pick Block player.attack=Attack / Break player.build=Place Block +player.fast_interaction=Accelerated interaction player.flight=Flight player.noclip=No-clip player.drop=Drop Item diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index c318af01..490ef4d7 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -38,7 +38,10 @@ menu.Page not found=Страница не найдена menu.Quit=Выход menu.Save and Quit to Menu=Сохранить и Выйти в Меню menu.Settings=Настройки +menu.Reset settings=Сбросить настройки menu.Contents Menu=Меню контентпаков +menu.Open data folder=Открыть папку данных +menu.Open content folder=Открыть папку [content] world.Seed=Зерно world.Name=Название @@ -57,6 +60,7 @@ settings.Ambient=Фон settings.Backlight=Подсветка settings.Camera Shaking=Тряска Камеры settings.Camera Inertia=Инерция Камеры +settings.Camera FOV Effects=Эффекты поля зрения settings.Fog Curve=Кривая Тумана settings.FOV=Поле Зрения settings.Fullscreen=Полный экран @@ -72,6 +76,7 @@ settings.Regular Sounds=Обычные Звуки settings.UI Sounds=Звуки Интерфейса settings.V-Sync=Вертикальная Синхронизация settings.Key=Кнопка +settings.Limit Background FPS=Ограничить фоновую частоту кадров # Управление chunks.reload=Перезагрузить Чанки @@ -88,6 +93,7 @@ hud.inventory=Инвентарь player.pick=Подобрать Блок player.attack=Атаковать / Сломать player.build=Поставить Блок +player.fast_interaction=Ускоренное взаимодействие player.flight=Полёт player.drop=Выбросить Предмет camera.zoom=Приближение diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp index 86d6fb22..2642e005 100644 --- a/src/assets/Assets.hpp +++ b/src/assets/Assets.hpp @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include #include +#include "util/stringutil.hpp" #include "graphics/core/TextureAnimation.hpp" class Assets; @@ -84,6 +86,15 @@ public: return static_cast(found->second.get()); } + template + T& require(const std::string& name) const { + T* asset = get(name); + if (asset == nullptr) { + throw std::runtime_error(util::quote(name) + " not found"); + } + return *asset; + } + template std::optional getMap() const { const auto& mapIter = assets.find(typeid(T)); diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 182bed6f..ce2d1c30 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -16,6 +16,7 @@ #include "objects/rigging.hpp" #include "util/ThreadPool.hpp" #include "voxels/Block.hpp" +#include "items/ItemDef.hpp" #include "Assets.hpp" #include "assetload_funcs.hpp" @@ -212,12 +213,6 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.tryAddSound(material.breakSound); } - addLayouts( - 0, - "core", - loader.getPaths()->getMainRoot() / fs::path("layouts"), - loader - ); for (auto& entry : content->getPacks()) { auto pack = entry.second.get(); auto& info = pack->getInfo(); @@ -228,7 +223,11 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { for (auto& entry : content->getSkeletons()) { auto& skeleton = *entry.second; for (auto& bone : skeleton.getBones()) { - auto& model = bone->model.name; + std::string model = bone->model.name; + size_t pos = model.rfind('.'); + if (pos != std::string::npos) { + model = model.substr(0, pos); + } if (!model.empty()) { loader.add( AssetType::MODEL, MODELS_FOLDER + "/" + model, model @@ -236,6 +235,15 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { } } } + for (const auto& [_, def] : content->items.getDefs()) { + if (def->modelName.find(':') == std::string::npos) { + loader.add( + AssetType::MODEL, + MODELS_FOLDER + "/" + def->modelName, + def->modelName + ); + } + } } } diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 0f69e64e..68b0289c 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -10,6 +10,7 @@ #include "coders/imageio.hpp" #include "coders/json.hpp" #include "coders/obj.hpp" +#include "coders/vec3.hpp" #include "constants.hpp" #include "debug/Logger.hpp" #include "files/engine_paths.hpp" @@ -23,6 +24,7 @@ #include "graphics/core/Texture.hpp" #include "graphics/core/TextureAnimation.hpp" #include "objects/rigging.hpp" +#include "util/stringutil.hpp" #include "Assets.hpp" #include "AssetsLoader.hpp" @@ -39,18 +41,32 @@ static bool animation( Atlas* dstAtlas ); -assetload::postfunc assetload:: - texture(AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr&) { - std::shared_ptr image( - imageio::read(paths->find(filename + ".png").u8string()).release() - ); - return [name, image](auto assets) { - assets->store(Texture::from(image.get()), name); - }; +assetload::postfunc assetload::texture( + AssetsLoader*, + const ResPaths* paths, + const std::string& filename, + const std::string& name, + const std::shared_ptr& +) { + auto actualFile = paths->find(filename + ".png").u8string(); + try { + std::shared_ptr image(imageio::read(actualFile).release()); + return [name, image, actualFile](auto assets) { + assets->store(Texture::from(image.get()), name); + }; + } catch (const std::runtime_error& err) { + logger.error() << actualFile << ": " << err.what(); + return [](auto) {}; + } } -assetload::postfunc assetload:: - shader(AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr&) { +assetload::postfunc assetload::shader( + AssetsLoader*, + const ResPaths* paths, + const std::string& filename, + const std::string& name, + const std::shared_ptr& +) { fs::path vertexFile = paths->find(filename + ".glslv"); fs::path fragmentFile = paths->find(filename + ".glslf"); @@ -181,8 +197,7 @@ assetload::postfunc assetload::sound( if (!fs::exists(variantFile)) { break; } - baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM) - ); + baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)); } auto sound = baseSound.release(); @@ -191,21 +206,50 @@ assetload::postfunc assetload::sound( }; } -assetload::postfunc assetload:: - model(AssetsLoader* loader, const ResPaths* paths, const std::string& file, const std::string& name, const std::shared_ptr&) { - auto path = paths->find(file + ".obj"); +static void request_textures(AssetsLoader* loader, const model::Model& model) { + for (auto& mesh : model.meshes) { + if (mesh.texture.find('$') == std::string::npos) { + auto filename = TEXTURES_FOLDER + "/" + mesh.texture; + loader->add( + AssetType::TEXTURE, filename, mesh.texture, nullptr + ); + } + } +} + +assetload::postfunc assetload::model( + AssetsLoader* loader, + const ResPaths* paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& +) { + auto path = paths->find(file + ".vec3"); + if (fs::exists(path)) { + auto bytes = files::read_bytes_buffer(path); + auto modelVEC3 = std::make_shared(vec3::load(path.u8string(), bytes)); + return [loader, name, modelVEC3=std::move(modelVEC3)](Assets* assets) { + for (auto& [modelName, model] : modelVEC3->models) { + request_textures(loader, model.model); + std::string fullName = name; + if (name != modelName) { + fullName += "." + modelName; + } + assets->store( + std::make_unique(model.model), + fullName + ); + logger.info() << "store model " << util::quote(modelName) + << " as " << util::quote(fullName); + } + }; + } + path = paths->find(file + ".obj"); auto text = files::read_string(path); try { auto model = obj::parse(path.u8string(), text).release(); return [=](Assets* assets) { - for (auto& mesh : model->meshes) { - if (mesh.texture.find('$') == std::string::npos) { - auto filename = TEXTURES_FOLDER + "/" + mesh.texture; - loader->add( - AssetType::TEXTURE, filename, mesh.texture, nullptr - ); - } - } + request_textures(loader, *model); assets->store(std::unique_ptr(model), name); }; } catch (const parsing_error& err) { diff --git a/src/assets/assets_util.cpp b/src/assets/assets_util.cpp new file mode 100644 index 00000000..bdd7795a --- /dev/null +++ b/src/assets/assets_util.cpp @@ -0,0 +1,24 @@ +#include "assets_util.hpp" + +#include "assets/Assets.hpp" +#include "graphics/core/Atlas.hpp" +#include "graphics/core/Texture.hpp" + +util::TextureRegion util::get_texture_region( + const Assets& assets, const std::string& name, const std::string& fallback +) { + size_t sep = name.find(':'); + if (sep == std::string::npos) { + return {assets.get(name), UVRegion(0,0,1,1)}; + } else { + auto atlas = assets.get(name.substr(0, sep)); + if (atlas) { + if (auto reg = atlas->getIf(name.substr(sep+1))) { + return {atlas->getTexture(), *reg}; + } else if (!fallback.empty()){ + return util::get_texture_region(assets, fallback, ""); + } + } + } + return {nullptr, UVRegion()}; +} diff --git a/src/assets/assets_util.hpp b/src/assets/assets_util.hpp new file mode 100644 index 00000000..db7dc361 --- /dev/null +++ b/src/assets/assets_util.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "maths/UVRegion.hpp" + +class Assets; +class Texture; + +namespace util { + struct TextureRegion { + const Texture* texture; + UVRegion region; + }; + + TextureRegion get_texture_region( + const Assets& assets, + const std::string& name, + const std::string& fallback + ); +} diff --git a/src/coders/byte_utils.cpp b/src/coders/byte_utils.cpp index 380c932c..e30c14f2 100644 --- a/src/coders/byte_utils.cpp +++ b/src/coders/byte_utils.cpp @@ -107,6 +107,14 @@ void ByteReader::checkMagic(const char* data, size_t size) { pos += size; } +void ByteReader::get(char* dst, size_t size) { + if (pos + size > this->size) { + throw std::runtime_error("buffer underflow"); + } + std::memcpy(dst, data+pos, size); + pos += size; +} + ubyte ByteReader::get() { if (pos == size) { throw std::runtime_error("buffer underflow"); diff --git a/src/coders/byte_utils.hpp b/src/coders/byte_utils.hpp index 103327c5..0532fe43 100644 --- a/src/coders/byte_utils.hpp +++ b/src/coders/byte_utils.hpp @@ -52,6 +52,8 @@ public: ByteReader(const ubyte* data); void checkMagic(const char* data, size_t size); + /// @brief Get N bytes + void get(char* dst, size_t size); /// @brief Read one byte (unsigned 8 bit integer) ubyte get(); /// @brief Read one byte (unsigned 8 bit integer) without pointer move diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index b79dc14e..985c3440 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -360,6 +360,13 @@ std::string BasicParser::parseString(char quote, bool closeRequired) { ss << (char)parseSimpleInt(8); continue; } + if (c == 'u') { + int codepoint = parseSimpleInt(16); + ubyte bytes[4]; + int size = util::encode_utf8(codepoint, bytes); + ss.write(reinterpret_cast(bytes), size); + continue; + } switch (c) { case 'n': ss << '\n'; break; case 'r': ss << '\r'; break; diff --git a/src/coders/vec3.cpp b/src/coders/vec3.cpp new file mode 100644 index 00000000..63edc21e --- /dev/null +++ b/src/coders/vec3.cpp @@ -0,0 +1,230 @@ +#include "vec3.hpp" + +#include + +#include "byte_utils.hpp" +#include "util/data_io.hpp" +#include "util/stringutil.hpp" +#include "graphics/core/Model.hpp" + +inline constexpr int VERSION = 1; + +inline constexpr int FLAG_ZLIB = 0x1; +inline constexpr int FLAG_16BIT_INDICES = 0x2; + +using namespace vec3; + +vec3::Model::~Model() = default; + +enum AttributeType { + POSITION = 0, + UV, + NORMAL, + COLOR +}; + +struct VertexAttribute { + AttributeType type; + int flags; + util::Buffer data; + + VertexAttribute() = default; + + VertexAttribute(VertexAttribute&&) = default; + + VertexAttribute& operator=(VertexAttribute&& o) { + type = o.type; + flags = o.flags; + data = std::move(o.data); + return *this; + } +}; + +static VertexAttribute load_attribute(ByteReader& reader) { + auto type = static_cast(reader.get()); + int flags = reader.get(); + assert(type >= POSITION && flags <= COLOR); + if (flags != 0) { + throw std::runtime_error("attribute compression is not supported yet"); + } + int size = reader.getInt32(); + + util::Buffer data(size / sizeof(float)); + reader.get(reinterpret_cast(data.data()), size); + if (dataio::is_big_endian()) { + for (int i = 0; i < data.size(); i++) { + data[i] = dataio::swap(data[i]); + } + } + return VertexAttribute {type, flags, std::move(data)}; +} + +static model::Mesh build_mesh( + const std::vector& attrs, + const util::Buffer& indices, + const std::string& texture +) { + const glm::vec3* coords = nullptr; + const glm::vec2* uvs = nullptr; + const glm::vec3* normals = nullptr; + + int coordsIndex, uvsIndex, normalsIndex; + + for (int i = 0; i < attrs.size(); i++) { + const auto& attr = attrs[i]; + switch (attr.type) { + case POSITION: + coords = reinterpret_cast(attr.data.data()); + coordsIndex = i; + break; + case UV: + uvs = reinterpret_cast(attr.data.data()); + uvsIndex = i; + break; + case NORMAL: + normals = reinterpret_cast(attr.data.data()); + normalsIndex = i; + break; + case COLOR: // unused + break; + } + } + std::vector vertices; + int attrsCount = attrs.size(); + int verticesCount = indices.size() / attrsCount; + for (int i = 0; i < verticesCount; i++) { + model::Vertex vertex {}; + if (coords) { + vertex.coord = coords[indices[i * attrsCount + coordsIndex]]; + } + if (uvs) { + vertex.uv = uvs[indices[i * attrsCount + uvsIndex]]; + } + if (normals) { + vertex.normal = normals[indices[i * attrsCount + normalsIndex]]; + } else if (coords) { + // Flat normal calculation + int idx = (i / 3) * 3; + auto a = coords[indices[idx * attrsCount + coordsIndex]]; + auto b = coords[indices[(idx + 1) * attrsCount + coordsIndex]]; + auto c = coords[indices[(idx + 2) * attrsCount + coordsIndex]]; + vertex.normal = glm::normalize(glm::cross(b - a, c - a)); + } + vertices.push_back(std::move(vertex)); + } + return model::Mesh {texture, std::move(vertices)}; +} + +static model::Mesh load_mesh( + ByteReader& reader, const std::vector& materials +) { + int triangleCount = reader.getInt32(); + int materialId = reader.getInt16(); + int flags = reader.getInt16(); + int attributeCount = reader.getInt16(); + if (flags == FLAG_ZLIB) { + throw std::runtime_error("compression is not supported yet"); + } + std::vector attributes; + for (int i = 0; i < attributeCount; i++) { + attributes.push_back(load_attribute(reader)); + } + + util::Buffer indices(triangleCount * 3 * attributeCount); + if ((flags & FLAG_16BIT_INDICES) == 0){ + util::Buffer smallIndices(indices.size()); + reader.get( + reinterpret_cast(smallIndices.data()), + indices.size() * sizeof(uint8_t) + ); + for (int i = 0; i < indices.size(); i++) { + indices[i] = smallIndices[i]; + } + } else { + reader.get( + reinterpret_cast(indices.data()), + indices.size() * sizeof(uint16_t) + ); + } + if (dataio::is_big_endian()) { + for (int i = 0; i < indices.size(); i++) { + indices[i] = dataio::swap(indices[i]); + } + } + return build_mesh( + attributes, + indices, + materials.at(materialId).name + ); +} + +static Model load_model( + ByteReader& reader, const std::vector& materials +) { + int nameLength = reader.getInt16(); + assert(nameLength >= 0); + float x = reader.getFloat32(); + float y = reader.getFloat32(); + float z = reader.getFloat32(); + int meshCount = reader.getInt32(); + assert(meshCount >= 0); + + std::vector meshes; + for (int i = 0; i < meshCount; i++) { + meshes.push_back(load_mesh(reader, materials)); + } + util::Buffer chars(nameLength); + reader.get(chars.data(), nameLength); + std::string name(chars.data(), nameLength); + + glm::vec3 offset {x, y, z}; + for (auto& mesh : meshes) { + for (auto& vertex : mesh.vertices) { + vertex.coord -= offset; + } + } + return Model {std::move(name), model::Model {std::move(meshes)}, {x, y, z}}; +} + +static Material load_material(ByteReader& reader) { + int flags = reader.getInt16(); + int nameLength = reader.getInt16(); + assert(nameLength >= 0); + util::Buffer chars(nameLength); + reader.get(chars.data(), nameLength); + std::string name(chars.data(), nameLength); + return Material {flags, std::move(name)}; +} + +File vec3::load( + const std::string_view file, const util::Buffer& src +) { + ByteReader reader(src.data(), src.size()); + + // Header + reader.checkMagic("\0\0VEC3\0\0", 8); + int version = reader.getInt16(); + int reserved = reader.getInt16(); + if (version > VERSION) { + throw std::runtime_error("unsupported VEC3 version"); + } + assert(reserved == 0); + + // Body + int materialCount = reader.getInt16(); + int modelCount = reader.getInt16(); + assert(materialCount >= 0); + assert(modelCount >= 0); + + std::vector materials; + for (int i = 0; i < materialCount; i++) { + materials.push_back(load_material(reader)); + } + + std::unordered_map models; + for (int i = 0; i < modelCount; i++) { + Model model = load_model(reader, materials); + models[model.name] = std::move(model); + } + return File {std::move(models), std::move(materials)}; +} diff --git a/src/coders/vec3.hpp b/src/coders/vec3.hpp new file mode 100644 index 00000000..1aa3ea9d --- /dev/null +++ b/src/coders/vec3.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "typedefs.hpp" +#include "util/Buffer.hpp" +#include "graphics/core/Model.hpp" + +/// See /doc/specs/vec3_model_spec.md +namespace vec3 { + struct Material { + int flags; + std::string name; + }; + + struct Model { + std::string name; + model::Model model; + glm::vec3 origin; + + Model& operator=(Model&&) = default; + + ~Model(); + }; + + struct File { + std::unordered_map models; + std::vector materials; + + File(File&&) = default; + + File& operator=(File&&) = default; + }; + + File load(const std::string_view file, const util::Buffer& src); +} diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 4290312d..895b28f9 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -44,6 +44,8 @@ std::unique_ptr ContentBuilder::build() { def.rt.hitboxes[i].push_back(aabb); } } + } else { + def.rt.hitboxes->emplace_back(AABB(glm::vec3(1.0f))); } blockDefsIndices.push_back(&def); diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ac7aa5ef..3406f6d9 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -326,6 +326,7 @@ void ContentLoader::loadBlock( root.at("ui-layout").get(def.uiLayout); root.at("inventory-size").get(def.inventorySize); root.at("tick-interval").get(def.tickInterval); + root.at("overlay-texture").get(def.overlayTexture); if (root.has("fields")) { def.dataStruct = std::make_unique(); @@ -418,17 +419,18 @@ void ContentLoader::loadItem( std::string iconTypeStr = ""; root.at("icon-type").get(iconTypeStr); if (iconTypeStr == "none") { - def.iconType = item_icon_type::none; + def.iconType = ItemIconType::NONE; } else if (iconTypeStr == "block") { - def.iconType = item_icon_type::block; + def.iconType = ItemIconType::BLOCK; } else if (iconTypeStr == "sprite") { - def.iconType = item_icon_type::sprite; + def.iconType = ItemIconType::SPRITE; } else if (iconTypeStr.length()) { logger.error() << name << ": unknown icon type" << iconTypeStr; } root.at("icon").get(def.icon); root.at("placing-block").get(def.placingBlock); root.at("script-name").get(def.scriptName); + root.at("model-name").get(def.modelName); root.at("stack-size").get(def.stackSize); // item light emission [r, g, b] where r,g,b in range [0..15] @@ -532,7 +534,7 @@ void ContentLoader::loadBlock( auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX); item.generated = true; item.caption = def.caption; - item.iconType = item_icon_type::block; + item.iconType = ItemIconType::BLOCK; item.icon = full; item.placingBlock = full; diff --git a/src/content/loading/GeneratorLoader.cpp b/src/content/loading/GeneratorLoader.cpp index 299565a4..ebae9118 100644 --- a/src/content/loading/GeneratorLoader.cpp +++ b/src/content/loading/GeneratorLoader.cpp @@ -202,6 +202,16 @@ void ContentLoader::loadGenerator( map.at("biome-parameters").get(def.biomeParameters); map.at("biome-bpd").get(def.biomesBPD); map.at("heights-bpd").get(def.heightsBPD); + std::string interpName; + map.at("heights-interpolation").get(interpName); + if (auto interp = InterpolationType_from(interpName)) { + def.heightsInterpolation = *interp; + } + map.at("biomes-interpolation").get(interpName); + if (auto interp = InterpolationType_from(interpName)) { + def.biomesInterpolation = *interp; + } + map.at("sea-level").get(def.seaLevel); map.at("wide-structs-chunks-radius").get(def.wideStructsChunksRadius); if (map.has("heightmap-inputs")) { diff --git a/src/core_defs.cpp b/src/core_defs.cpp index 7d19acd1..c62900f7 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -25,13 +25,13 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) { } { ItemDef& item = builder->items.create(CORE_EMPTY); - item.iconType = item_icon_type::none; + item.iconType = ItemIconType::NONE; } auto bindsFile = paths->getResourcesFolder()/fs::path("bindings.toml"); if (fs::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), files::read_string(bindsFile) + bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND ); } @@ -43,7 +43,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) { block.hitboxes = {AABB()}; block.breakable = false; ItemDef& item = builder->items.create(CORE_OBSTACLE+".item"); - item.iconType = item_icon_type::block; + item.iconType = ItemIconType::BLOCK; item.icon = CORE_OBSTACLE; item.placingBlock = CORE_OBSTACLE; item.caption = block.caption; @@ -59,7 +59,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) { block.hitboxes = {AABB()}; block.obstacle = false; ItemDef& item = builder->items.create(CORE_STRUCT_AIR+".item"); - item.iconType = item_icon_type::block; + item.iconType = ItemIconType::BLOCK; item.icon = CORE_STRUCT_AIR; item.placingBlock = CORE_STRUCT_AIR; item.caption = block.caption; diff --git a/src/core_defs.hpp b/src/core_defs.hpp index 0f25de3a..c2b5dd39 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -27,6 +27,8 @@ inline const std::string BIND_PLAYER_FLIGHT = "player.flight"; inline const std::string BIND_PLAYER_ATTACK = "player.attack"; 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"; class EnginePaths; diff --git a/src/data/setting.hpp b/src/data/setting.hpp index 2053254a..7068ebf7 100644 --- a/src/data/setting.hpp +++ b/src/data/setting.hpp @@ -57,6 +57,10 @@ public: return value; } + const T& getDefault() const { + return initial; + } + T& operator*() { return value; } diff --git a/src/engine.cpp b/src/engine.cpp index 6ba4fcee..0cfc6733 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -20,6 +20,7 @@ #include "frontend/menu.hpp" #include "frontend/screens/Screen.hpp" #include "frontend/screens/MenuScreen.hpp" +#include "graphics/render/ModelsGenerator.hpp" #include "graphics/core/Batch2D.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/ImageData.hpp" @@ -131,7 +132,7 @@ void Engine::loadControls() { if (fs::is_regular_file(controls_file)) { logger.info() << "loading controls"; std::string text = files::read_string(controls_file); - Events::loadBindings(controls_file.u8string(), text); + Events::loadBindings(controls_file.u8string(), text, BindType::BIND); } } @@ -184,8 +185,11 @@ void Engine::mainloop() { if (!Window::isIconified()) { renderFrame(batch); } - Window::setFramerate(Window::isIconified() ? 20 : - settings.display.framerate.get()); + Window::setFramerate( + Window::isIconified() && settings.display.limitFpsIconified.get() + ? 20 + : settings.display.framerate.get() + ); processPostRunnables(); @@ -280,6 +284,17 @@ void Engine::loadAssets() { } } assets = std::move(new_assets); + + if (content) { + for (auto& [name, def] : content->items.getDefs()) { + assets->store( + std::make_unique( + ModelsGenerator::generate(*def, *content, *assets) + ), + name + ".model" + ); + } + } } static void load_configs(const fs::path& root) { @@ -287,12 +302,14 @@ static void load_configs(const fs::path& root) { auto bindsFile = configFolder/fs::path("bindings.toml"); if (fs::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), files::read_string(bindsFile) + bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND ); } } void Engine::loadContent() { + scripting::cleanup(); + auto resdir = paths->getResourcesFolder(); std::vector names; @@ -338,6 +355,7 @@ void Engine::loadContent() { } void Engine::resetContent() { + scripting::cleanup(); auto resdir = paths->getResourcesFolder(); std::vector resRoots; { @@ -388,6 +406,9 @@ double Engine::getDelta() const { } void Engine::setScreen(std::shared_ptr screen) { + // unblock all bindings + Events::enableBindings(); + // reset audio channels (stop all sources) audio::reset_channel(audio::get_channel_index("regular")); audio::reset_channel(audio::get_channel_index("ambient")); this->screen = std::move(screen); diff --git a/src/files/files.cpp b/src/files/files.cpp index 8be834b5..f7d3f460 100644 --- a/src/files/files.cpp +++ b/src/files/files.cpp @@ -75,7 +75,11 @@ std::unique_ptr files::read_bytes( const fs::path& filename, size_t& length ) { std::ifstream input(filename, std::ios::binary); - if (!input.is_open()) return nullptr; + if (!input.is_open()) { + throw std::runtime_error( + "could not to load file '" + filename.string() + "'" + ); + } input.seekg(0, std::ios_base::end); length = input.tellg(); input.seekg(0, std::ios_base::beg); @@ -102,16 +106,11 @@ std::vector files::read_bytes(const fs::path& filename) { std::string files::read_string(const fs::path& filename) { size_t size; - std::unique_ptr bytes(read_bytes(filename, size)); - if (bytes == nullptr) { - throw std::runtime_error( - "could not to load file '" + filename.string() + "'" - ); - } + auto bytes = read_bytes(filename, size); return std::string((const char*)bytes.get(), size); } -bool files::write_string(const fs::path& filename, const std::string content) { +bool files::write_string(const fs::path& filename, std::string_view content) { std::ofstream file(filename); if (!file) { return false; diff --git a/src/files/files.hpp b/src/files/files.hpp index 99ccc936..4cf509d4 100644 --- a/src/files/files.hpp +++ b/src/files/files.hpp @@ -38,7 +38,7 @@ namespace files { uint append_bytes(const fs::path& file, const ubyte* data, size_t size); /// @brief Write string to the file - bool write_string(const fs::path& filename, const std::string content); + bool write_string(const fs::path& filename, std::string_view content); /// @brief Write dynamic data to the JSON file /// @param nice if true, human readable format will be used, otherwise diff --git a/src/files/settings_io.cpp b/src/files/settings_io.cpp index 7d04d693..d2a85f6a 100644 --- a/src/files/settings_io.cpp +++ b/src/files/settings_io.cpp @@ -51,6 +51,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("samples", &settings.display.samples); builder.add("framerate", &settings.display.framerate); builder.add("fullscreen", &settings.display.fullscreen); + builder.add("limit-fps-iconified", &settings.display.limitFpsIconified); builder.section("camera"); builder.add("sensitivity", &settings.camera.sensitivity); @@ -102,6 +103,26 @@ dv::value SettingsHandler::getValue(const std::string& name) const { } } +dv::value SettingsHandler::getDefault(const std::string& name) const { + auto found = map.find(name); + if (found == map.end()) { + throw std::runtime_error("setting '" + name + "' does not exist"); + } + auto setting = found->second; + + if (auto number = dynamic_cast(setting)) { + return static_cast(number->getDefault()); + } else if (auto integer = dynamic_cast(setting)) { + return static_cast(integer->getDefault()); + } else if (auto flag = dynamic_cast(setting)) { + return flag->getDefault(); + } else if (auto string = dynamic_cast(setting)) { + return string->getDefault(); + } else { + throw std::runtime_error("type is not implemented for '" + name + "'"); + } +} + std::string SettingsHandler::toString(const std::string& name) const { auto found = map.find(name); if (found == map.end()) { diff --git a/src/files/settings_io.hpp b/src/files/settings_io.hpp index b5d17df6..cc5b33c9 100644 --- a/src/files/settings_io.hpp +++ b/src/files/settings_io.hpp @@ -22,6 +22,7 @@ public: SettingsHandler(EngineSettings& settings); dv::value getValue(const std::string& name) const; + dv::value getDefault(const std::string& name) const; void setValue(const std::string& name, const dv::value& value); std::string toString(const std::string& name) const; Setting* getSetting(const std::string& name) const; diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 68f308c8..6c54a83d 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -37,10 +37,10 @@ LevelFrontend::LevelFrontend( auto soundsCamera = currentPlayer->currentCamera.get(); if (soundsCamera == currentPlayer->spCamera.get() || soundsCamera == currentPlayer->tpCamera.get()) { - soundsCamera = currentPlayer->camera.get(); + soundsCamera = currentPlayer->fpCamera.get(); } bool relative = player == currentPlayer && - soundsCamera == currentPlayer->camera.get(); + soundsCamera == currentPlayer->fpCamera.get(); if (!relative) { pos = player->getPosition(); } diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 2ff4d53c..c58f31ac 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -224,7 +224,7 @@ void Hud::processInput(bool visible) { setPause(true); } } - if (!pause && Events::active(BIND_DEVTOOLS_CONSOLE)) { + if (!pause && Events::jactive(BIND_DEVTOOLS_CONSOLE)) { showOverlay(assets->get("core:console"), false); } if (!Window::isFocused() && !pause && !isInventoryOpen()) { diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 73e235d7..c0d4aa88 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -37,7 +37,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr level) auto menu = engine->getGUI()->getMenu(); menu->reset(); - controller = std::make_unique(settings, std::move(level)); + controller = std::make_unique(engine, std::move(level)); frontend = std::make_unique(controller->getPlayer(), controller.get(), assets); worldRenderer = std::make_unique(engine, frontend.get(), controller->getPlayer()); @@ -48,7 +48,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr level) worldRenderer->clear(); })); keepAlive(settings.camera.fov.observe([=](double value) { - controller->getPlayer()->camera->setFov(glm::radians(value)); + controller->getPlayer()->fpCamera->setFov(glm::radians(value)); })); keepAlive(Events::getBinding(BIND_CHUNKS_RELOAD).onactived.add([=](){ controller->getLevel()->chunks->saveAndClear(); @@ -93,7 +93,7 @@ void LevelScreen::saveWorldPreview() { int previewSize = settings.ui.worldPreviewSize.get(); // camera special copy for world preview - Camera camera = *player->camera; + Camera camera = *player->fpCamera; camera.setFov(glm::radians(70.0f)); DrawContext pctx(nullptr, {Window::width, Window::height}, batch.get()); @@ -101,7 +101,7 @@ void LevelScreen::saveWorldPreview() { Viewport viewport(previewSize * 1.5, previewSize); DrawContext ctx(&pctx, viewport, batch.get()); - worldRenderer->draw(ctx, &camera, false, true, 0.0f, postProcessing.get()); + worldRenderer->draw(ctx, camera, false, true, 0.0f, postProcessing.get()); auto image = postProcessing->toImage(); image->flipY(); imageio::write(paths->resolve("world:preview.png").u8string(), image.get()); @@ -164,7 +164,9 @@ void LevelScreen::draw(float delta) { Viewport viewport(Window::width, Window::height); DrawContext ctx(nullptr, viewport, batch.get()); - worldRenderer->draw(ctx, camera.get(), hudVisible, hud->isPause(), delta, postProcessing.get()); + worldRenderer->draw( + ctx, *camera, hudVisible, hud->isPause(), delta, postProcessing.get() + ); if (hudVisible) { hud->draw(ctx); diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp index d9cc0ec2..13b48586 100644 --- a/src/graphics/core/Batch2D.cpp +++ b/src/graphics/core/Batch2D.cpp @@ -75,7 +75,7 @@ void Batch2D::vertex( buffer[index++] = a; } -void Batch2D::texture(Texture* new_texture){ +void Batch2D::texture(const Texture* new_texture){ if (currentTexture == new_texture) { return; } diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp index 4e38ffe5..2877f5bd 100644 --- a/src/graphics/core/Batch2D.hpp +++ b/src/graphics/core/Batch2D.hpp @@ -17,7 +17,7 @@ class Batch2D : public Flushable { std::unique_ptr blank; size_t index; glm::vec4 color; - Texture* currentTexture; + const Texture* currentTexture; DrawPrimitive primitive = DrawPrimitive::triangle; UVRegion region {0.0f, 0.0f, 1.0f, 1.0f}; @@ -40,7 +40,7 @@ public: ~Batch2D(); void begin(); - void texture(Texture* texture); + void texture(const Texture* texture); void untexture(); void setRegion(UVRegion region); void sprite(float x, float y, float w, float h, const UVRegion& region, glm::vec4 tint); diff --git a/src/graphics/core/Batch3D.cpp b/src/graphics/core/Batch3D.cpp index d3e369a0..dd6261e5 100644 --- a/src/graphics/core/Batch3D.cpp +++ b/src/graphics/core/Batch3D.cpp @@ -106,7 +106,7 @@ void Batch3D::face( tint.r, tint.g, tint.b, tint.a); } -void Batch3D::texture(Texture* new_texture){ +void Batch3D::texture(const Texture* new_texture){ if (currentTexture == new_texture) return; flush(); diff --git a/src/graphics/core/Batch3D.hpp b/src/graphics/core/Batch3D.hpp index 2cfe4bcc..bd5f7b4e 100644 --- a/src/graphics/core/Batch3D.hpp +++ b/src/graphics/core/Batch3D.hpp @@ -18,7 +18,7 @@ class Batch3D : public Flushable { std::unique_ptr blank; size_t index; - Texture* currentTexture; + const Texture* currentTexture; void vertex( float x, float y, float z, @@ -47,11 +47,36 @@ public: ~Batch3D(); void begin(); - void texture(Texture* texture); - void sprite(glm::vec3 pos, glm::vec3 up, glm::vec3 right, float w, float h, const UVRegion& uv, glm::vec4 tint); - void xSprite(float w, float h, const UVRegion& uv, const glm::vec4 tint, bool shading=true); - void cube(const glm::vec3 coords, const glm::vec3 size, const UVRegion(&texfaces)[6], const glm::vec4 tint, bool shading=true); - void blockCube(const glm::vec3 size, const UVRegion(&texfaces)[6], const glm::vec4 tint, bool shading=true); + void texture(const Texture* texture); + void sprite( + glm::vec3 pos, + glm::vec3 up, + glm::vec3 right, + float w, + float h, + const UVRegion& uv, + glm::vec4 tint + ); + void xSprite( + float w, + float h, + const UVRegion& uv, + const glm::vec4 tint, + bool shading = true + ); + void cube( + const glm::vec3 coords, + const glm::vec3 size, + const UVRegion (&texfaces)[6], + const glm::vec4 tint, + bool shading = true + ); + void blockCube( + const glm::vec3 size, + const UVRegion (&texfaces)[6], + const glm::vec4 tint, + bool shading = true + ); void vertex(glm::vec3 pos, glm::vec2 uv, glm::vec4 tint); void point(glm::vec3 pos, glm::vec4 tint); void flush() override; diff --git a/src/graphics/core/Cubemap.cpp b/src/graphics/core/Cubemap.cpp index 5da69e3e..3267b25e 100644 --- a/src/graphics/core/Cubemap.cpp +++ b/src/graphics/core/Cubemap.cpp @@ -30,10 +30,10 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat) } } -void Cubemap::bind(){ +void Cubemap::bind() const { glBindTexture(GL_TEXTURE_CUBE_MAP, id); } -void Cubemap::unbind() { +void Cubemap::unbind() const { glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } diff --git a/src/graphics/core/Cubemap.hpp b/src/graphics/core/Cubemap.hpp index 72d29a21..3a80bda8 100644 --- a/src/graphics/core/Cubemap.hpp +++ b/src/graphics/core/Cubemap.hpp @@ -7,6 +7,6 @@ class Cubemap : public GLTexture { public: Cubemap(uint width, uint height, ImageFormat format); - virtual void bind() override; - virtual void unbind() override; + virtual void bind() const override; + virtual void unbind() const override; }; diff --git a/src/graphics/core/GLTexture.cpp b/src/graphics/core/GLTexture.cpp index 9572f415..4781536e 100644 --- a/src/graphics/core/GLTexture.cpp +++ b/src/graphics/core/GLTexture.cpp @@ -33,11 +33,11 @@ GLTexture::~GLTexture() { glDeleteTextures(1, &id); } -void GLTexture::bind(){ +void GLTexture::bind() const { glBindTexture(GL_TEXTURE_2D, id); } -void GLTexture::unbind() { +void GLTexture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/src/graphics/core/GLTexture.hpp b/src/graphics/core/GLTexture.hpp index 7f9e8f22..b5f82384 100644 --- a/src/graphics/core/GLTexture.hpp +++ b/src/graphics/core/GLTexture.hpp @@ -10,8 +10,8 @@ public: GLTexture(const ubyte* data, uint width, uint height, ImageFormat format); virtual ~GLTexture(); - virtual void bind() override; - virtual void unbind() override; + virtual void bind() const override; + virtual void unbind() const override; virtual void reload(const ubyte* data); void setNearestFilter(); diff --git a/src/graphics/core/Model.cpp b/src/graphics/core/Model.cpp index 78e15354..3da5d897 100644 --- a/src/graphics/core/Model.cpp +++ b/src/graphics/core/Model.cpp @@ -29,6 +29,11 @@ void Mesh::addBox(glm::vec3 pos, glm::vec3 size) { addPlane(pos-X*size, Z*size, Y*size, -X); } +void Mesh::scale(const glm::vec3& size) { + for (auto& vertex : vertices) { + vertex.coord *= size; + } +} void Model::clean() { meshes.erase( diff --git a/src/graphics/core/Model.hpp b/src/graphics/core/Model.hpp index 786b9ac9..b92fb030 100644 --- a/src/graphics/core/Model.hpp +++ b/src/graphics/core/Model.hpp @@ -17,6 +17,7 @@ namespace model { void addPlane(glm::vec3 pos, glm::vec3 right, glm::vec3 up, glm::vec3 norm); void addBox(glm::vec3 pos, glm::vec3 size); + void scale(const glm::vec3& size); }; struct Model { diff --git a/src/graphics/core/Texture.hpp b/src/graphics/core/Texture.hpp index 4b3f1651..ba5b066e 100644 --- a/src/graphics/core/Texture.hpp +++ b/src/graphics/core/Texture.hpp @@ -17,8 +17,8 @@ public: virtual ~Texture() {} - virtual void bind() = 0; - virtual void unbind() = 0; + virtual void bind() const = 0; + virtual void unbind() const = 0; virtual void reload(const ImageData& image) = 0; diff --git a/src/graphics/render/ModelBatch.cpp b/src/graphics/render/ModelBatch.cpp index bda65de3..f6a1b58b 100644 --- a/src/graphics/render/ModelBatch.cpp +++ b/src/graphics/render/ModelBatch.cpp @@ -1,5 +1,6 @@ #include "ModelBatch.hpp" +#include "assets/assets_util.hpp" #include "graphics/core/Mesh.hpp" #include "graphics/core/Model.hpp" #include "graphics/core/Atlas.hpp" @@ -77,6 +78,7 @@ void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix, const texture_names_map* varTextures, bool backlight) { glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + gpos += lightsOffset; light_t light = chunks->getLight( std::floor(gpos.x), std::floor(std::min(CHUNK_H-1.0f, gpos.y)), @@ -137,9 +139,13 @@ void ModelBatch::render() { entries.clear(); } +void ModelBatch::setLightsOffset(const glm::vec3& offset) { + lightsOffset = offset; +} + void ModelBatch::setTexture(const std::string& name, const texture_names_map* varTextures) { - if (name.at(0) == '$') { + if (varTextures && name.at(0) == '$') { const auto& found = varTextures->find(name); if (found == varTextures->end()) { return setTexture(nullptr); @@ -147,25 +153,13 @@ void ModelBatch::setTexture(const std::string& name, return setTexture(found->second, varTextures); } } - size_t sep = name.find(':'); - if (sep == std::string::npos) { - setTexture(assets->get(name)); - } else { - auto atlas = assets->get(name.substr(0, sep)); - if (atlas == nullptr) { - setTexture(nullptr); - } else { - setTexture(atlas->getTexture()); - if (auto reg = atlas->getIf(name.substr(sep+1))) { - region = *reg; - } else { - setTexture("blocks:notfound", varTextures); - } - } - } + + auto textureRegion = util::get_texture_region(*assets, name, "blocks:notfound"); + setTexture(textureRegion.texture); + region = textureRegion.region; } -void ModelBatch::setTexture(Texture* texture) { +void ModelBatch::setTexture(const Texture* texture) { if (texture == nullptr) { texture = blank.get(); } diff --git a/src/graphics/render/ModelBatch.hpp b/src/graphics/render/ModelBatch.hpp index fc3b1295..523eb300 100644 --- a/src/graphics/render/ModelBatch.hpp +++ b/src/graphics/render/ModelBatch.hpp @@ -31,9 +31,10 @@ class ModelBatch { Assets* assets; Chunks* chunks; - Texture* texture = nullptr; + const Texture* texture = nullptr; UVRegion region {0.0f, 0.0f, 1.0f, 1.0f}; const EngineSettings* settings; + glm::vec3 lightsOffset {}; static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f}; @@ -71,7 +72,7 @@ class ModelBatch { bool backlight); void setTexture(const std::string& name, const texture_names_map* varTextures); - void setTexture(Texture* texture); + void setTexture(const Texture* texture); void flush(); struct DrawEntry { @@ -96,4 +97,6 @@ public: const model::Model* model, const texture_names_map* varTextures); void render(); + + void setLightsOffset(const glm::vec3& offset); }; diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp new file mode 100644 index 00000000..edec061c --- /dev/null +++ b/src/graphics/render/ModelsGenerator.cpp @@ -0,0 +1,74 @@ +#include "ModelsGenerator.hpp" + +#include "assets/Assets.hpp" +#include "items/ItemDef.hpp" +#include "voxels/Block.hpp" +#include "content/Content.hpp" +#include "debug/Logger.hpp" + +static debug::Logger logger("models-generator"); + +static void configure_textures( + model::Model& model, + const Block& blockDef, + const Assets& assets +) { + for (auto& mesh : model.meshes) { + auto& texture = mesh.texture; + if (texture.empty() || texture.at(0) != '$') { + continue; + } + try { + int index = std::stoi(texture.substr(1)); + texture = "blocks:"+blockDef.textureFaces.at(index); + } catch (const std::invalid_argument& err) { + } catch (const std::runtime_error& err) { + logger.error() << err.what(); + } + } +} + +static model::Model create_flat_model( + const std::string& texture, const Assets& assets +) { + auto model = assets.require("drop-item"); + for (auto& mesh : model.meshes) { + if (mesh.texture == "$0") { + mesh.texture = texture; + } + } + return model; +} + +model::Model ModelsGenerator::generate( + const ItemDef& def, const Content& content, const Assets& assets +) { + if (def.iconType == ItemIconType::BLOCK) { + auto model = assets.require("block"); + const auto& blockDef = content.blocks.require(def.icon); + if (blockDef.model == BlockModel::xsprite) { + return create_flat_model( + "blocks:" + blockDef.textureFaces.at(0), assets + ); + } + for (auto& mesh : model.meshes) { + switch (blockDef.model) { + case BlockModel::aabb: { + glm::vec3 size = blockDef.hitboxes.at(0).size(); + float m = glm::max(size.x, glm::max(size.y, size.z)); + m = glm::min(1.0f, m); + mesh.scale(size / m); + break; + } default: + break; + } + mesh.scale(glm::vec3(0.3f)); + } + configure_textures(model, blockDef, assets); + return model; + } else if (def.iconType == ItemIconType::SPRITE) { + return create_flat_model(def.icon, assets); + } else { + return model::Model(); + } +} diff --git a/src/graphics/render/ModelsGenerator.hpp b/src/graphics/render/ModelsGenerator.hpp new file mode 100644 index 00000000..ec7ea873 --- /dev/null +++ b/src/graphics/render/ModelsGenerator.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "graphics/core/Model.hpp" + +struct ItemDef; +class Assets; +class Content; + +class ModelsGenerator { +public: + static model::Model generate( + const ItemDef& def, const Content& content, const Assets& assets + ); +}; diff --git a/src/graphics/render/Skybox.cpp b/src/graphics/render/Skybox.cpp index 420f0ec7..22927b3c 100644 --- a/src/graphics/render/Skybox.cpp +++ b/src/graphics/render/Skybox.cpp @@ -58,11 +58,13 @@ Skybox::Skybox(uint size, Shader* shader) Skybox::~Skybox() = default; -void Skybox::drawBackground(Camera* camera, Assets* assets, int width, int height) { - auto backShader = assets->get("background"); +void Skybox::drawBackground( + const Camera& camera, const Assets& assets, int width, int height +) { + auto backShader = assets.get("background"); backShader->use(); - backShader->uniformMatrix("u_view", camera->getView(false)); - backShader->uniform1f("u_zoom", camera->zoom*camera->getFov()/(M_PI*0.5f)); + backShader->uniformMatrix("u_view", camera.getView(false)); + backShader->uniform1f("u_zoom", camera.zoom*camera.getFov()/(M_PI*0.5f)); backShader->uniform1f("u_ar", float(width)/float(height)); backShader->uniform1i("u_cubemap", 1); bind(); @@ -93,8 +95,8 @@ void Skybox::drawStars(float angle, float opacity) { void Skybox::draw( const DrawContext& pctx, - Camera* camera, - Assets* assets, + const Camera& camera, + const Assets& assets, float daytime, float fog) { @@ -107,9 +109,9 @@ void Skybox::draw( DrawContext ctx = pctx.sub(); ctx.setBlendMode(BlendMode::addition); - auto p_shader = assets->get("ui3d"); + auto p_shader = assets.get("ui3d"); p_shader->use(); - p_shader->uniformMatrix("u_projview", camera->getProjView(false)); + p_shader->uniformMatrix("u_projview", camera.getProjView(false)); p_shader->uniformMatrix("u_apply", glm::mat4(1.0f)); batch3d->begin(); @@ -117,7 +119,7 @@ void Skybox::draw( float opacity = glm::pow(1.0f-fog, 7.0f); for (auto& sprite : sprites) { - batch3d->texture(assets->get(sprite.texture)); + batch3d->texture(assets.get(sprite.texture)); float sangle = daytime * float(M_PI)*2.0 + sprite.phase; float distance = sprite.distance; @@ -136,6 +138,7 @@ void Skybox::draw( } void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) { + float dayTime = t; DrawContext ctx = pctx.sub(); ctx.setDepthMask(false); ctx.setDepthTest(false); @@ -180,10 +183,12 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) }; t *= M_PI*2.0f; + lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); shader->uniform1i("u_quality", quality); shader->uniform1f("u_mie", mie); shader->uniform1f("u_fog", mie - 1.0f); - shader->uniform3f("u_lightDir", glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f))); + shader->uniform3f("u_lightDir", lightDir); + shader->uniform1f("u_dayTime", dayTime); for (uint face = 0; face < 6; face++) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubemap->getId(), 0); shader->uniform3f("u_xaxis", xaxs[face]); diff --git a/src/graphics/render/Skybox.hpp b/src/graphics/render/Skybox.hpp index 01bffab7..2781a95a 100644 --- a/src/graphics/render/Skybox.hpp +++ b/src/graphics/render/Skybox.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "typedefs.hpp" #include "maths/fastmaths.hpp" @@ -27,21 +28,24 @@ class Skybox { Shader* shader; bool ready = false; FastRandom random; + glm::vec3 lightDir; std::unique_ptr mesh; std::unique_ptr batch3d; std::vector sprites; void drawStars(float angle, float opacity); - void drawBackground(Camera* camera, Assets* assets, int width, int height); + void drawBackground( + const Camera& camera, const Assets& assets, int width, int height + ); public: Skybox(uint size, Shader* shader); ~Skybox(); void draw( const DrawContext& pctx, - Camera* camera, - Assets* assets, + const Camera& camera, + const Assets& assets, float daytime, float fog ); @@ -52,4 +56,8 @@ public: bool isReady() const { return ready; } + + const glm::vec3 getLightDir() const { + return lightDir; + } }; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 56cd662b..4b13a23f 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -9,6 +9,7 @@ #include #include "assets/Assets.hpp" +#include "assets/assets_util.hpp" #include "content/Content.hpp" #include "engine.hpp" #include "frontend/LevelFrontend.hpp" @@ -78,17 +79,17 @@ WorldRenderer::WorldRenderer( WorldRenderer::~WorldRenderer() = default; bool WorldRenderer::drawChunk( - size_t index, Camera* camera, Shader* shader, bool culling + size_t index, const Camera& camera, Shader* shader, bool culling ) { auto chunk = level->chunks->getChunks()[index]; if (!chunk->flags.lighted) { return false; } float distance = glm::distance( - camera->position, + camera.position, glm::vec3( (chunk->x + 0.5f) * CHUNK_W, - camera->position.y, + camera.position.y, (chunk->z + 0.5f) * CHUNK_D ) ); @@ -113,7 +114,9 @@ bool WorldRenderer::drawChunk( return true; } -void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) { +void WorldRenderer::drawChunks( + Chunks* chunks, const Camera& camera, Shader* shader +) { auto assets = engine->getAssets(); auto atlas = assets->get("blocks"); @@ -127,8 +130,8 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) { if (chunks->getChunks()[i] == nullptr) continue; indices.emplace_back(i); } - float px = camera->position.x / static_cast(CHUNK_W) - 0.5f; - float pz = camera->position.z / static_cast(CHUNK_D) - 0.5f; + float px = camera.position.x / static_cast(CHUNK_W) - 0.5f; + float pz = camera.position.z / static_cast(CHUNK_D) - 0.5f; std::sort(indices.begin(), indices.end(), [chunks, px, pz](auto i, auto j) { const auto& chunksBuffer = chunks->getChunks(); const auto a = chunksBuffer[i].get(); @@ -141,7 +144,7 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) { }); bool culling = engine->getSettings().graphics.frustumCulling.get(); if (culling) { - frustumCulling->update(camera->getProjView()); + frustumCulling->update(camera.getProjView()); } chunks->visible = 0; for (size_t i = 0; i < indices.size(); i++) { @@ -151,20 +154,21 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) { void WorldRenderer::setupWorldShader( Shader* shader, - Camera* camera, + const Camera& camera, const EngineSettings& settings, float fogFactor ) { shader->use(); shader->uniformMatrix("u_model", glm::mat4(1.0f)); - shader->uniformMatrix("u_proj", camera->getProjection()); - shader->uniformMatrix("u_view", camera->getView()); + shader->uniformMatrix("u_proj", camera.getProjection()); + shader->uniformMatrix("u_view", camera.getView()); shader->uniform1f("u_timer", timer); shader->uniform1f("u_gamma", settings.graphics.gamma.get()); shader->uniform1f("u_fogFactor", fogFactor); shader->uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader->uniform1f("u_dayTime", level->getWorld()->getInfo().daytime); - shader->uniform3f("u_cameraPos", camera->position); + shader->uniform2f("u_lightDir", skybox->getLightDir()); + shader->uniform3f("u_cameraPos", camera.position); shader->uniform1i("u_cubemap", 1); auto indices = level->content->getIndices(); @@ -186,7 +190,7 @@ void WorldRenderer::setupWorldShader( void WorldRenderer::renderLevel( const DrawContext&, - Camera* camera, + const Camera& camera, const EngineSettings& settings, float delta, bool pause @@ -251,10 +255,10 @@ void WorldRenderer::renderBlockSelection() { } void WorldRenderer::renderLines( - Camera* camera, Shader* linesShader, const DrawContext& pctx + const Camera& camera, Shader* linesShader, const DrawContext& pctx ) { linesShader->use(); - linesShader->uniformMatrix("u_projview", camera->getProjView()); + linesShader->uniformMatrix("u_projview", camera.getProjView()); if (player->selection.vox.id != BLOCK_VOID) { renderBlockSelection(); } @@ -268,7 +272,7 @@ void WorldRenderer::renderLines( } void WorldRenderer::renderDebugLines( - const DrawContext& pctx, Camera* camera, Shader* linesShader + const DrawContext& pctx, const Camera& camera, Shader* linesShader ) { DrawContext ctx = pctx.sub(lineBatch.get()); const auto& viewport = ctx.getViewport(); @@ -280,8 +284,8 @@ void WorldRenderer::renderDebugLines( linesShader->use(); if (showChunkBorders) { - linesShader->uniformMatrix("u_projview", camera->getProjView()); - glm::vec3 coord = player->camera->position; + linesShader->uniformMatrix("u_projview", camera.getProjView()); + glm::vec3 coord = player->fpCamera->position; if (coord.x < 0) coord.x--; if (coord.z < 0) coord.z--; int cx = floordiv(static_cast(coord.x), CHUNK_W); @@ -310,7 +314,7 @@ void WorldRenderer::renderDebugLines( -length, length ) * model * - glm::inverse(camera->rotation) + glm::inverse(camera.rotation) ); ctx.setDepthTest(false); @@ -327,9 +331,70 @@ void WorldRenderer::renderDebugLines( lineBatch->line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f); } +void WorldRenderer::renderHands(const Camera& camera, const Assets& assets) { + auto entityShader = assets.get("entity"); + auto indices = level->content->getIndices(); + + // get current chosen item + const auto& inventory = player->getInventory(); + int slot = player->getChosenSlot(); + const ItemStack& stack = inventory->getSlot(slot); + const auto& def = indices->items.require(stack.getItemId()); + + // prepare modified HUD camera + Camera hudcam = camera; + hudcam.far = 100.0f; + hudcam.setFov(1.2f); + hudcam.position = {}; + + // configure model matrix + const glm::vec3 itemOffset(0.08f, 0.035f, -0.1); + + static glm::mat4 prevRotation(1.0f); + + const float speed = 24.0f; + glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset); + matrix = glm::scale(matrix, glm::vec3(0.1f)); + glm::mat4 rotation = camera.rotation; + glm::quat rot0 = glm::quat_cast(prevRotation); + glm::quat rot1 = glm::quat_cast(rotation); + glm::quat finalRot = + glm::slerp(rot0, rot1, static_cast(engine->getDelta() * speed)); + rotation = glm::mat4_cast(finalRot); + matrix = rotation * matrix * + glm::rotate( + glm::mat4(1.0f), -glm::pi() * 0.5f, glm::vec3(0, 1, 0) + ); + prevRotation = rotation; + auto offset = -(camera.position - player->getPosition()); + float angle = glm::radians(player->cam.x - 90); + float cos = glm::cos(angle); + float sin = glm::sin(angle); + + float newX = offset.x * cos - offset.z * sin; + float newZ = offset.x * sin + offset.z * cos; + offset = glm::vec3(newX, offset.y, newZ); + matrix = matrix * glm::translate(glm::mat4(1.0f), offset); + + // render + modelBatch->setLightsOffset(camera.position); + modelBatch->draw( + matrix, + glm::vec3(1.0f), + assets.get(def.modelName), + nullptr + ); + Window::clearDepth(); + setupWorldShader(entityShader, hudcam, engine->getSettings(), 0.0f); + skybox->bind(); + modelBatch->render(); + modelBatch->setLightsOffset(glm::vec3()); + skybox->unbind(); +} + void WorldRenderer::draw( const DrawContext& pctx, - Camera* camera, + Camera& camera, bool hudVisible, bool pause, float delta, @@ -338,15 +403,15 @@ void WorldRenderer::draw( timer += delta * !pause; auto world = level->getWorld(); const Viewport& vp = pctx.getViewport(); - camera->aspect = vp.getWidth() / static_cast(vp.getHeight()); + camera.aspect = vp.getWidth() / static_cast(vp.getHeight()); const auto& settings = engine->getSettings(); const auto& worldInfo = world->getInfo(); skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4); - auto assets = engine->getAssets(); - auto linesShader = assets->get("lines"); + const auto& assets = *engine->getAssets(); + auto linesShader = assets.get("lines"); // World render scope with diegetic HUD included { @@ -367,22 +432,70 @@ void WorldRenderer::draw( // Debug lines if (hudVisible) { renderLines(camera, linesShader, ctx); + if (player->currentCamera == player->fpCamera) { + renderHands(camera, assets); + } } } - if (hudVisible && player->debug) { renderDebugLines(wctx, camera, linesShader); } + renderBlockOverlay(wctx, assets); } // Rendering fullscreen quad with - auto screenShader = assets->get("screen"); + auto screenShader = assets.get("screen"); screenShader->use(); screenShader->uniform1f("u_timer", timer); screenShader->uniform1f("u_dayTime", worldInfo.daytime); postProcessing->render(pctx, screenShader); } +void WorldRenderer::renderBlockOverlay(const DrawContext& wctx, const Assets& assets) { + int x = std::floor(player->currentCamera->position.x); + int y = std::floor(player->currentCamera->position.y); + int z = std::floor(player->currentCamera->position.z); + auto block = level->chunks->get(x, y, z); + if (block && block->id) { + const auto& def = + level->content->getIndices()->blocks.require(block->id); + if (def.overlayTexture.empty()) { + return; + } + auto textureRegion = util::get_texture_region( + assets, def.overlayTexture, "blocks:notfound" + ); + DrawContext ctx = wctx.sub(); + ctx.setDepthTest(false); + ctx.setCullFace(false); + + auto& shader = assets.require("ui3d"); + shader.use(); + batch3d->begin(); + shader.uniformMatrix("u_projview", glm::mat4(1.0f)); + shader.uniformMatrix("u_apply", glm::mat4(1.0f)); + auto light = level->chunks->getLight(x, y, z); + float s = Lightmap::extract(light, 3) / 15.0f; + glm::vec4 tint( + glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s), + glm::min(1.0f, Lightmap::extract(light, 1) / 15.0f + s), + glm::min(1.0f, Lightmap::extract(light, 2) / 15.0f + s), + 1.0f + ); + batch3d->texture(textureRegion.texture); + batch3d->sprite( + glm::vec3(), + glm::vec3(0, 1, 0), + glm::vec3(1, 0, 0), + 2, + 2, + textureRegion.region, + tint + ); + batch3d->flush(); + } +} + void WorldRenderer::drawBorders( int sx, int sy, int sz, int ex, int ey, int ez ) { diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 06e4bb63..c5d7040e 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -23,6 +23,7 @@ class Skybox; class PostProcessing; class DrawContext; class ModelBatch; +class Assets; struct EngineSettings; namespace model { @@ -41,16 +42,20 @@ class WorldRenderer { std::unique_ptr modelBatch; float timer = 0.0f; - bool drawChunk(size_t index, Camera* camera, Shader* shader, bool culling); - void drawChunks(Chunks* chunks, Camera* camera, Shader* shader); + bool drawChunk(size_t index, const Camera& camera, Shader* shader, bool culling); + void drawChunks(Chunks* chunks, const Camera& camera, Shader* shader); /// @brief Render block selection lines void renderBlockSelection(); + + void renderHands(const Camera& camera, const Assets& assets); /// @brief Render lines (selection and debug) /// @param camera active camera /// @param linesShader shader used - void renderLines(Camera* camera, Shader* linesShader, const DrawContext& pctx); + void renderLines( + const Camera& camera, Shader* linesShader, const DrawContext& pctx + ); /// @brief Render all debug lines (chunks borders, coord system guides) /// @param context graphics context @@ -58,13 +63,15 @@ class WorldRenderer { /// @param linesShader shader used void renderDebugLines( const DrawContext& context, - Camera* camera, + const Camera& camera, Shader* linesShader ); + void renderBlockOverlay(const DrawContext& context, const Assets& assets); + void setupWorldShader( Shader* shader, - Camera* camera, + const Camera& camera, const EngineSettings& settings, float fogFactor ); @@ -77,7 +84,7 @@ public: void draw( const DrawContext& context, - Camera* camera, + Camera& camera, bool hudVisible, bool pause, float delta, @@ -91,7 +98,7 @@ public: /// @param settings engine settings void renderLevel( const DrawContext& context, - Camera* camera, + const Camera& camera, const EngineSettings& settings, float delta, bool pause diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index 1b759a57..fa3d836c 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -1,6 +1,7 @@ #include "InventoryView.hpp" #include "assets/Assets.hpp" +#include "assets/assets_util.hpp" #include "content/Content.hpp" #include "frontend/LevelFrontend.hpp" #include "frontend/locale.hpp" @@ -161,9 +162,9 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) { auto& item = indices->items.require(stack.getItemId()); switch (item.iconType) { - case item_icon_type::none: + case ItemIconType::NONE: break; - case item_icon_type::block: { + case ItemIconType::BLOCK: { const Block& cblock = content->blocks.require(item.icon); batch->texture(previews->getTexture()); @@ -173,23 +174,14 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) { 0, 0, 0, region, false, true, tint); break; } - case item_icon_type::sprite: { - size_t index = item.icon.find(':'); - std::string name = item.icon.substr(index+1); - UVRegion region(0.0f, 0.0, 1.0f, 1.0f); - if (index == std::string::npos) { - batch->texture(assets->get(name)); - } else { - std::string atlasname = item.icon.substr(0, index); - auto atlas = assets->get(atlasname); - if (atlas && atlas->has(name)) { - region = atlas->get(name); - batch->texture(atlas->getTexture()); - } - } + case ItemIconType::SPRITE: { + auto textureRegion = + util::get_texture_region(*assets, item.icon, "blocks:notfound"); + + batch->texture(textureRegion.texture); batch->rect( pos.x, pos.y, slotSize, slotSize, - 0, 0, 0, region, false, true, tint); + 0, 0, 0, textureRegion.region, false, true, tint); break; } } diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 12c59508..128d3ea6 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -170,7 +170,9 @@ void TextBox::paste(const std::wstring& text) { input.erase(std::remove(input.begin(), input.end(), '\r'), input.end()); refreshLabel(); setCaret(caret + text.length()); - validate(); + if (validate()) { + onInput(); + } } /// @brief Remove part of the text and move caret to start of the part @@ -470,6 +472,12 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) { } } +void TextBox::onInput() { + if (subconsumer) { + subconsumer(input); + } +} + void TextBox::performEditingKeyboardEvents(keycode key) { bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT); bool breakSelection = getSelectionLength() != 0 && !shiftPressed; @@ -480,19 +488,23 @@ void TextBox::performEditingKeyboardEvents(keycode key) { } input = input.substr(0, caret-1) + input.substr(caret); setCaret(caret-1); - validate(); + if (validate()) { + onInput(); + } } } else if (key == keycode::DELETE) { if (!eraseSelected() && caret < input.length()) { input = input.substr(0, caret) + input.substr(caret + 1); - validate(); + if (validate()) { + onInput(); + } } } else if (key == keycode::ENTER) { if (multiline) { paste(L"\n"); } else { defocus(); - if (validate() && consumer) { + if (validate()) { consumer(label->getText()); } } @@ -591,6 +603,10 @@ void TextBox::setTextConsumer(wstringconsumer consumer) { this->consumer = std::move(consumer); } +void TextBox::setTextSubConsumer(wstringconsumer consumer) { + this->subconsumer = std::move(consumer); +} + void TextBox::setTextValidator(wstringchecker validator) { this->validator = std::move(validator); } diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 376244cb..c7898307 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -17,6 +17,7 @@ namespace gui { std::wstring placeholder; wstringsupplier supplier = nullptr; wstringconsumer consumer = nullptr; + wstringconsumer subconsumer = nullptr; wstringchecker validator = nullptr; runnable onEditStart = nullptr; runnable onUpPressed; @@ -65,6 +66,8 @@ namespace gui { void performEditingKeyboardEvents(keycode key); void refreshLabel(); + + void onInput(); public: TextBox( std::wstring placeholder, @@ -79,6 +82,10 @@ namespace gui { /// @param consumer std::wstring consumer function virtual void setTextConsumer(wstringconsumer consumer); + /// @brief Sub-consumer called while editing text + /// @param consumer std::wstring consumer function + virtual void setTextSubConsumer(wstringconsumer consumer); + /// @brief Text validator called while text editing and returns true if /// text is valid /// @param validator std::wstring consumer returning boolean diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index 215be1ef..777ee5bc 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -355,6 +355,13 @@ static std::shared_ptr readTextBox(UiXmlReader& reader, const xml::xmlel reader.getFilename() )); } + if (element->has("sub-consumer")) { + textbox->setTextSubConsumer(scripting::create_wstring_consumer( + reader.getEnvironment(), + element->attr("sub-consumer").getText(), + reader.getFilename() + )); + } if (element->has("supplier")) { textbox->setTextSupplier(scripting::create_wstring_supplier( reader.getEnvironment(), diff --git a/src/items/ItemDef.cpp b/src/items/ItemDef.cpp index 321f7be7..8d0cdd66 100644 --- a/src/items/ItemDef.cpp +++ b/src/items/ItemDef.cpp @@ -14,4 +14,5 @@ void ItemDef::cloneTo(ItemDef& dst) { dst.icon = icon; dst.placingBlock = placingBlock; dst.scriptName = scriptName; + dst.modelName = modelName; } diff --git a/src/items/ItemDef.hpp b/src/items/ItemDef.hpp index a306dce2..999b190f 100644 --- a/src/items/ItemDef.hpp +++ b/src/items/ItemDef.hpp @@ -12,10 +12,10 @@ struct item_funcs_set { bool on_block_break_by : 1; }; -enum class item_icon_type { - none, // invisible (core:empty) must not be rendered - sprite, // textured quad: icon is `atlas_name:texture_name` - block, // block preview: icon is string block id +enum class ItemIconType { + NONE, // invisible (core:empty) must not be rendered + SPRITE, // textured quad: icon is `atlas_name:texture_name` + BLOCK, // block preview: icon is string block id }; struct ItemDef { @@ -29,12 +29,14 @@ struct ItemDef { bool generated = false; uint8_t emission[4] {0, 0, 0, 0}; - item_icon_type iconType = item_icon_type::sprite; + ItemIconType iconType = ItemIconType::SPRITE; std::string icon = "blocks:notfound"; std::string placingBlock = "core:air"; std::string scriptName = name.substr(name.find(':') + 1); + std::string modelName = name + ".model"; + struct { itemid_t id; blockid_t placingBlock; diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index f0bee4c5..2cd491d4 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -3,6 +3,7 @@ #include #include "debug/Logger.hpp" +#include "engine.hpp" #include "files/WorldFiles.hpp" #include "interfaces/Object.hpp" #include "objects/Entities.hpp" @@ -15,10 +16,8 @@ static debug::Logger logger("level-control"); -LevelController::LevelController( - EngineSettings& settings, std::unique_ptr level -) - : settings(settings), +LevelController::LevelController(Engine* engine, std::unique_ptr level) + : settings(engine->getSettings()), level(std::move(level)), blocks(std::make_unique( this->level.get(), settings.chunks.padding.get() @@ -27,7 +26,7 @@ LevelController::LevelController( this->level.get(), settings.chunks.padding.get() )), player(std::make_unique( - this->level.get(), settings, blocks.get() + settings, this->level.get(), blocks.get() )) { scripting::on_world_load(this); } diff --git a/src/logic/LevelController.hpp b/src/logic/LevelController.hpp index a2c5af3e..94e0ac2c 100644 --- a/src/logic/LevelController.hpp +++ b/src/logic/LevelController.hpp @@ -6,6 +6,7 @@ #include "ChunksController.hpp" #include "PlayerController.hpp" +class Engine; class Level; class Player; struct EngineSettings; @@ -19,7 +20,7 @@ class LevelController { std::unique_ptr chunks; std::unique_ptr player; public: - LevelController(EngineSettings& settings, std::unique_ptr level); + LevelController(Engine* engine, std::unique_ptr level); /// @param delta time elapsed since the last update /// @param input is user input allowed to be handled diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 35d92b95..00e4d1a7 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -6,6 +6,7 @@ #include "content/Content.hpp" #include "core_defs.hpp" +#include "settings.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" #include "items/ItemStack.hpp" @@ -26,6 +27,7 @@ #include "BlocksController.hpp" #include "scripting/scripting.hpp" +const float INTERACTION_RELOAD = 0.160f; const float STEPS_SPEED = 2.2f; const float CAM_SHAKE_OFFSET = 0.0075f; const float CAM_SHAKE_OFFSET_Y = 0.031f; @@ -41,7 +43,7 @@ CameraControl::CameraControl( const std::shared_ptr& player, const CameraSettings& settings ) : player(player), - camera(player->camera), + camera(player->fpCamera), settings(settings), offset(0.0f, 0.7f, 0.0f) { } @@ -187,11 +189,10 @@ void CameraControl::update(PlayerInput input, float delta, Chunks* chunks) { } PlayerController::PlayerController( - Level* level, - const EngineSettings& settings, + const EngineSettings& settings, Level* level, BlocksController* blocksController ) - : level(level), + : settings(settings), level(level), player(level->getObject(0)), camControl(player, settings.camera), blocksController(blocksController) { @@ -262,7 +263,7 @@ void PlayerController::postUpdate(float delta, bool input, bool pause) { player->postUpdate(); camControl.update(this->input, pause ? 0.0f : delta, level->chunks.get()); if (input) { - updateInteraction(); + updateInteraction(delta); } else { player->selection = {}; } @@ -353,7 +354,7 @@ static void pick_block( voxel* PlayerController::updateSelection(float maxDistance) { auto indices = level->content->getIndices(); auto chunks = level->chunks.get(); - auto camera = player->camera.get(); + auto camera = player->fpCamera.get(); auto& selection = player->selection; glm::vec3 end; @@ -416,7 +417,7 @@ voxel* PlayerController::updateSelection(float maxDistance) { void PlayerController::processRightClick(const Block& def, const Block& target) { const auto& selection = player->selection; auto chunks = level->chunks.get(); - auto camera = player->camera.get(); + auto camera = player->fpCamera.get(); blockstate state {}; state.rotation = determine_rotation(&def, selection.normal, camera->dir); @@ -480,17 +481,24 @@ void PlayerController::updateEntityInteraction( } } -void PlayerController::updateInteraction() { +void PlayerController::updateInteraction(float delta) { auto indices = level->content->getIndices(); auto chunks = level->chunks.get(); const auto& selection = player->selection; - - bool xkey = Events::pressed(keycode::X); - bool lclick = Events::jactive(BIND_PLAYER_ATTACK) || - (xkey && Events::active(BIND_PLAYER_ATTACK)); - bool rclick = Events::jactive(BIND_PLAYER_BUILD) || - (xkey && Events::active(BIND_PLAYER_BUILD)); + + if (interactionTimer > 0.0f) { + interactionTimer -= delta; + } + bool xkey = Events::active(BIND_PLAYER_FAST_INTERACTOIN); float maxDistance = xkey ? 200.0f : 10.0f; + bool longInteraction = interactionTimer <= 0 || xkey; + bool lclick = Events::jactive(BIND_PLAYER_ATTACK) || + (longInteraction && Events::active(BIND_PLAYER_ATTACK)); + bool rclick = Events::jactive(BIND_PLAYER_BUILD) || + (longInteraction && Events::active(BIND_PLAYER_BUILD)); + if (lclick || rclick) { + interactionTimer = INTERACTION_RELOAD; + } auto inventory = player->getInventory(); const ItemStack& stack = inventory->getSlot(player->getChosenSlot()); diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index 306e2336..6affa43d 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -6,6 +6,7 @@ #include "objects/Player.hpp" +class Engine; class Camera; class Level; class Block; @@ -13,6 +14,7 @@ class Chunks; class BlocksController; struct Hitbox; struct CameraSettings; +struct EngineSettings; class CameraControl { std::shared_ptr player; @@ -45,17 +47,19 @@ public: }; class PlayerController { + const EngineSettings& settings; Level* level; std::shared_ptr player; PlayerInput input {}; CameraControl camControl; BlocksController* blocksController; + float interactionTimer = 0.0f; void updateKeyboard(); void resetKeyboard(); void updatePlayer(float delta); void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick); - void updateInteraction(); + void updateInteraction(float delta); float stepsTimer = 0.0f; void onFootstep(const Hitbox& hitbox); @@ -65,9 +69,7 @@ class PlayerController { voxel* updateSelection(float maxDistance); public: PlayerController( - Level* level, - const EngineSettings& settings, - BlocksController* blocksController + const EngineSettings& settings, Level* level, BlocksController* blocksController ); void update(float delta, bool input, bool pause); void postUpdate(float delta, bool input, bool pause); diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index 2ac9c1e8..f9c8b3f8 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -35,6 +35,7 @@ extern const luaL_Reg playerlib[]; extern const luaL_Reg quatlib[]; // quat.cpp extern const luaL_Reg timelib[]; extern const luaL_Reg tomllib[]; +extern const luaL_Reg utf8lib[]; extern const luaL_Reg vec2lib[]; // vecn.cpp extern const luaL_Reg vec3lib[]; // vecn.cpp extern const luaL_Reg vec4lib[]; // vecn.cpp diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 6f0dd8b8..ee611b63 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -154,6 +154,8 @@ static int l_get_setting_info(lua::State* L) { lua::setfield(L, "min"); lua::pushnumber(L, number->getMax()); lua::setfield(L, "max"); + lua::pushnumber(L, number->getDefault()); + lua::setfield(L, "def"); return 1; } if (auto integer = dynamic_cast(setting)) { @@ -161,12 +163,32 @@ static int l_get_setting_info(lua::State* L) { lua::setfield(L, "min"); lua::pushinteger(L, integer->getMax()); lua::setfield(L, "max"); + lua::pushinteger(L, integer->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto boolean = dynamic_cast(setting)) { + lua::pushboolean(L, boolean->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto string = dynamic_cast(setting)) { + lua::pushstring(L, string->getDefault()); + lua::setfield(L, "def"); return 1; } lua::pop(L); throw std::runtime_error("unsupported setting type"); } +#include "util/platform.hpp" + +static int l_open_folder(lua::State* L) { + auto path = engine->getPaths()->resolve(lua::require_string(L, 1)); + platform::open_folder(path); + return 0; +} + /// @brief Quit the game static int l_quit(lua::State*) { Window::setShouldClose(true); @@ -184,5 +206,6 @@ const luaL_Reg corelib[] = { {"set_setting", lua::wrap}, {"str_setting", lua::wrap}, {"get_setting_info", lua::wrap}, + {"open_folder", lua::wrap}, {"quit", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index 845e4e96..b6fef820 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -34,6 +34,14 @@ static int l_def_name(lua::State* L) { } return 0; } + +static int l_def_hitbox(lua::State* L) { + if (auto def = require_entity_def(L)) { + return lua::pushvec(L, def->hitbox); + } + return 0; +} + static int l_defs_count(lua::State* L) { return lua::pushinteger(L, indices->entities.count()); } @@ -202,6 +210,7 @@ const luaL_Reg entitylib[] = { {"exists", lua::wrap}, {"def_index", lua::wrap}, {"def_name", lua::wrap}, + {"def_hitbox", lua::wrap}, {"get_def", lua::wrap}, {"defs_count", lua::wrap}, {"spawn", lua::wrap}, diff --git a/src/logic/scripting/lua/libs/libgeneration.cpp b/src/logic/scripting/lua/libs/libgeneration.cpp index ca97815c..034f6597 100644 --- a/src/logic/scripting/lua/libs/libgeneration.cpp +++ b/src/logic/scripting/lua/libs/libgeneration.cpp @@ -45,6 +45,7 @@ static int l_load_fragment(lua::State* L) { auto fragment = std::make_shared(); fragment->deserialize(map); + fragment->prepare(*content); return lua::newuserdata(L, std::move(fragment)); } diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index 84dde256..1866a433 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -1,4 +1,7 @@ +#include + #include "engine.hpp" +#include "files/files.hpp" #include "frontend/hud.hpp" #include "frontend/screens/Screen.hpp" #include "graphics/ui/GUI.hpp" @@ -109,6 +112,38 @@ static int l_is_pressed(lua::State* L) { } } +static void resetPackBindings(fs::path& packFolder) { + auto configFolder = packFolder/fs::path("config"); + auto bindsFile = configFolder/fs::path("bindings.toml"); + if (fs::is_regular_file(bindsFile)) { + Events::loadBindings( + bindsFile.u8string(), + files::read_string(bindsFile), + BindType::REBIND + ); + } +} + +static int l_reset_bindings(lua::State*) { + auto resFolder = engine->getPaths()->getResourcesFolder(); + resetPackBindings(resFolder); + for (auto& pack : engine->getContentPacks()) { + resetPackBindings(pack.folder); + } + return 0; +} + +static int l_set_enabled(lua::State* L) { + std::string bindname = lua::require_string(L, 1); + bool enable = lua::toboolean(L, 2); + const auto& bind = Events::bindings.find(bindname); + if (bind == Events::bindings.end()) { + throw std::runtime_error("unknown binding " + util::quote(bindname)); + } + Events::bindings[bindname].enable = enable; + return 0; +} + const luaL_Reg inputlib[] = { {"keycode", lua::wrap}, {"mousecode", lua::wrap}, @@ -118,4 +153,6 @@ const luaL_Reg inputlib[] = { {"get_binding_text", lua::wrap}, {"is_active", lua::wrap}, {"is_pressed", lua::wrap}, + {"reset_bindings", lua::wrap}, + {"set_enabled", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/libs/libitem.cpp b/src/logic/scripting/lua/libs/libitem.cpp index 5a4ef4de..c4ac49ec 100644 --- a/src/logic/scripting/lua/libs/libitem.cpp +++ b/src/logic/scripting/lua/libs/libitem.cpp @@ -10,55 +10,84 @@ static const ItemDef* get_item_def(lua::State* L, int idx) { return indices->items.get(id); } -static int l_item_name(lua::State* L) { +static int l_name(lua::State* L) { if (auto def = get_item_def(L, 1)) { return lua::pushstring(L, def->name); } return 0; } -static int l_item_index(lua::State* L) { +static int l_index(lua::State* L) { auto name = lua::require_string(L, 1); return lua::pushinteger(L, content->items.require(name).rt.id); } -static int l_item_stack_size(lua::State* L) { +static int l_stack_size(lua::State* L) { if (auto def = get_item_def(L, 1)) { return lua::pushinteger(L, def->stackSize); } return 0; } -static int l_item_defs_count(lua::State* L) { +static int l_defs_count(lua::State* L) { return lua::pushinteger(L, indices->items.count()); } -static int l_item_get_icon(lua::State* L) { +static int l_get_icon(lua::State* L) { if (auto def = get_item_def(L, 1)) { switch (def->iconType) { - case item_icon_type::none: + case ItemIconType::NONE: return 0; - case item_icon_type::sprite: + case ItemIconType::SPRITE: return lua::pushstring(L, def->icon); - case item_icon_type::block: + case ItemIconType::BLOCK: return lua::pushstring(L, "block-previews:" + def->icon); } } return 0; } -static int l_item_caption(lua::State* L) { +static int l_caption(lua::State* L) { if (auto def = get_item_def(L, 1)) { return lua::pushstring(L, def->caption); } return 0; } +static int l_placing_block(lua::State* L) { + if (auto def = get_item_def(L, 1)) { + return lua::pushinteger(L, def->rt.placingBlock); + } + return 0; +} + +static int l_model_name(lua::State* L) { + if (auto def = get_item_def(L, 1)) { + return lua::pushstring(L, def->modelName); + } + return 0; +} + +static int l_emission(lua::State* L) { + if (auto def = get_item_def(L, 1)) { + lua::createtable(L, 4, 0); + for (int i = 0; i < 4; ++i) { + lua::pushinteger(L, def->emission[i]); + lua::rawseti(L, i+1); + } + return 1; + } + return 0; +} + const luaL_Reg itemlib[] = { - {"index", lua::wrap}, - {"name", lua::wrap}, - {"stack_size", lua::wrap}, - {"defs_count", lua::wrap}, - {"icon", lua::wrap}, - {"caption", lua::wrap}, + {"index", lua::wrap}, + {"name", lua::wrap}, + {"stack_size", lua::wrap}, + {"defs_count", lua::wrap}, + {"icon", lua::wrap}, + {"caption", lua::wrap}, + {"placing_block", lua::wrap}, + {"model_name", lua::wrap}, + {"emission", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index fb4c9f0f..0d0a7223 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -85,7 +85,7 @@ static int l_set_rot(lua::State* L) { static int l_get_dir(lua::State* L) { if (auto player = get_player(L, 1)) { - return lua::pushvec3(L, player->camera->front); + return lua::pushvec3(L, player->fpCamera->front); } return 0; } diff --git a/src/logic/scripting/lua/libs/libutf8.cpp b/src/logic/scripting/lua/libs/libutf8.cpp new file mode 100644 index 00000000..3512624f --- /dev/null +++ b/src/logic/scripting/lua/libs/libutf8.cpp @@ -0,0 +1,92 @@ +#include "api_lua.hpp" + +#include +#include + +#include "../lua_custom_types.hpp" +#include "util/stringutil.hpp" + +static int l_encode(lua::State* L) { + std::string_view string = lua::require_string(L, 1); + if (lua::toboolean(L, 2)) { + lua::createtable(L, string.length(), 0); + for (size_t i = 0; i < string.length(); i++) { + lua::pushinteger(L, string[i] & 0xFF); + lua::rawseti(L, i+1); + } + } else { + lua::newuserdata(L, string.length()); + auto bytearray = lua::touserdata(L, -1); + bytearray->data().reserve(string.length()); + std::memcpy(bytearray->data().data(), string.data(), string.length()); + } + return 1; +} + +static int l_decode(lua::State* L) { + if (lua::istable(L, 1)) { + size_t size = lua::objlen(L, 1); + util::Buffer buffer(size); + return lua::pushstring(L, std::string(buffer.data(), size)); + } else if (auto bytes = lua::touserdata(L, 1)) { + return lua::pushstring( + L, + std::string( + reinterpret_cast(bytes->data().data()), + bytes->data().size() + ) + ); + } + return 1; +} + +static int l_length(lua::State* L) { + auto string = lua::require_string(L, 1); + return lua::pushinteger(L, util::length_utf8(string)); +} + +static int l_codepoint(lua::State* L) { + std::string_view string = lua::require_string(L, 1); + if (string.empty()) { + return lua::pushinteger(L, 0); + } + uint size; + return lua::pushinteger(L, util::decode_utf8(size, string.data())); +} + +static int l_sub(lua::State* L) { + auto string = util::str2u32str_utf8(lua::require_string(L, 1)); + int start = std::max(0, static_cast(lua::tointeger(L, 2) - 1)); + int end = string.length(); + if (lua::gettop(L) >= 3) { + end = std::max(0, static_cast(lua::tointeger(L, 3) - 1)); + } + return lua::pushstring(L, util::u32str2str_utf8(string.substr(start, end))); +} + +static int l_upper(lua::State* L) { + auto string = util::str2u32str_utf8(lua::require_string(L, 1)); + for (auto& c : string) { + c = std::towupper(c); + } + return lua::pushstring(L, util::u32str2str_utf8(string)); +} + +static int l_lower(lua::State* L) { + auto string = util::str2u32str_utf8(lua::require_string(L, 1)); + for (auto& c : string) { + c = std::towlower(c); + } + return lua::pushstring(L, util::u32str2str_utf8(string)); +} + +const luaL_Reg utf8lib[] = { + {"tobytes", lua::wrap}, + {"tostring", lua::wrap}, + {"length", lua::wrap}, + {"codepoint", lua::wrap}, + {"sub", lua::wrap}, + {"upper", lua::wrap}, + {"lower", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index d9ee3412..04249299 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "assets/Assets.hpp" @@ -12,7 +13,14 @@ using namespace scripting; namespace fs = std::filesystem; -static int l_world_get_list(lua::State* L) { +static WorldInfo& require_world_info() { + if (level == nullptr) { + throw std::runtime_error("no world open"); + } + return level->getWorld()->getInfo(); +} + +static int l_get_list(lua::State* L) { auto paths = engine->getPaths(); auto worlds = paths->scanForWorlds(); @@ -41,59 +49,64 @@ static int l_world_get_list(lua::State* L) { return 1; } -static int l_world_get_total_time(lua::State* L) { - return lua::pushnumber(L, level->getWorld()->getInfo().totalTime); +static int l_get_total_time(lua::State* L) { + return lua::pushnumber(L, require_world_info().totalTime); } -static int l_world_get_day_time(lua::State* L) { - return lua::pushnumber(L, level->getWorld()->getInfo().daytime); +static int l_get_day_time(lua::State* L) { + return lua::pushnumber(L, require_world_info().daytime); } -static int l_world_set_day_time(lua::State* L) { +static int l_set_day_time(lua::State* L) { auto value = lua::tonumber(L, 1); - level->getWorld()->getInfo().daytime = std::fmod(value, 1.0); + require_world_info().daytime = std::fmod(value, 1.0); return 0; } -static int l_world_set_day_time_speed(lua::State* L) { +static int l_set_day_time_speed(lua::State* L) { auto value = lua::tonumber(L, 1); - level->getWorld()->getInfo().daytimeSpeed = std::abs(value); + require_world_info().daytimeSpeed = std::abs(value); return 0; } -static int l_world_get_day_time_speed(lua::State* L) { - return lua::pushnumber(L, level->getWorld()->getInfo().daytimeSpeed); +static int l_get_day_time_speed(lua::State* L) { + return lua::pushnumber(L, require_world_info().daytimeSpeed); } -static int l_world_get_seed(lua::State* L) { - return lua::pushinteger(L, level->getWorld()->getSeed()); +static int l_get_seed(lua::State* L) { + return lua::pushinteger(L, require_world_info().seed); } -static int l_world_exists(lua::State* L) { +static int l_exists(lua::State* L) { auto name = lua::require_string(L, 1); auto worldsDir = engine->getPaths()->getWorldFolderByName(name); return lua::pushboolean(L, fs::is_directory(worldsDir)); } -static int l_world_is_day(lua::State* L) { - auto daytime = level->getWorld()->getInfo().daytime; +static int l_is_day(lua::State* L) { + auto daytime = require_world_info().daytime; return lua::pushboolean(L, daytime >= 0.333 && daytime <= 0.833); } -static int l_world_is_night(lua::State* L) { - auto daytime = level->getWorld()->getInfo().daytime; +static int l_is_night(lua::State* L) { + auto daytime = require_world_info().daytime; return lua::pushboolean(L, daytime < 0.333 || daytime > 0.833); } +static int l_get_generator(lua::State* L) { + return lua::pushstring(L, require_world_info().generator); +} + const luaL_Reg worldlib[] = { - {"get_list", lua::wrap}, - {"get_total_time", lua::wrap}, - {"get_day_time", lua::wrap}, - {"set_day_time", lua::wrap}, - {"set_day_time_speed", lua::wrap}, - {"get_day_time_speed", lua::wrap}, - {"get_seed", lua::wrap}, - {"is_day", lua::wrap}, - {"is_night", lua::wrap}, - {"exists", lua::wrap}, + {"get_list", lua::wrap}, + {"get_total_time", lua::wrap}, + {"get_day_time", lua::wrap}, + {"set_day_time", lua::wrap}, + {"set_day_time_speed", lua::wrap}, + {"get_day_time_speed", lua::wrap}, + {"get_seed", lua::wrap}, + {"get_generator", lua::wrap}, + {"is_day", lua::wrap}, + {"is_night", lua::wrap}, + {"exists", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index fd40e612..fe3e75b5 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -51,6 +51,7 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "quat", quatlib); openlib(L, "time", timelib); openlib(L, "toml", tomllib); + openlib(L, "utf8", utf8lib); openlib(L, "vec2", vec2lib); openlib(L, "vec3", vec3lib); openlib(L, "vec4", vec4lib); diff --git a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp index cdefa4f0..a59986ac 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp @@ -229,8 +229,10 @@ static int l_resize(lua::State* L) { uint height = touinteger(L, 3); auto interpName = tostring(L, 4); auto interpolation = InterpolationType::NEAREST; - if (!std::strcmp(interpName, "linear")) { + if (std::strcmp(interpName, "linear") == 0) { interpolation = InterpolationType::LINEAR; + } else if (std::strcmp(interpName, "cubic") == 0) { + interpolation = InterpolationType::CUBIC; } heightmap->getHeightmap()->resize(width, height, interpolation); } diff --git a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp index eb6a24ab..f123be6f 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp @@ -4,6 +4,7 @@ #include "world/generator/VoxelFragment.hpp" #include "util/stringutil.hpp" +#include "world/Level.hpp" using namespace lua; @@ -20,8 +21,20 @@ static int l_crop(lua::State* L) { return 0; } +static int l_place(lua::State* L) { + if (auto fragment = touserdata(L, 1)) { + auto offset = tovec3(L, 2); + int rotation = tointeger(L, 3) & 0b11; + fragment->getFragment()->place( + *scripting::level->chunks, offset, rotation + ); + } + return 0; +} + static std::unordered_map methods { {"crop", lua::wrap}, + {"place", lua::wrap}, }; static int l_meta_tostring(lua::State* L) { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 8456e220..70a397b4 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -164,32 +164,40 @@ void scripting::on_world_load(LevelController* controller) { auto L = lua::get_main_state(); for (auto& pack : scripting::engine->getContentPacks()) { - lua::emit_event(L, pack.id + ".worldopen"); + lua::emit_event(L, pack.id + ":.worldopen"); } } void scripting::on_world_tick() { auto L = lua::get_main_state(); for (auto& pack : scripting::engine->getContentPacks()) { - lua::emit_event(L, pack.id + ".worldtick"); + lua::emit_event(L, pack.id + ":.worldtick"); } } void scripting::on_world_save() { auto L = lua::get_main_state(); for (auto& pack : scripting::engine->getContentPacks()) { - lua::emit_event(L, pack.id + ".worldsave"); + lua::emit_event(L, pack.id + ":.worldsave"); } } void scripting::on_world_quit() { auto L = lua::get_main_state(); for (auto& pack : scripting::engine->getContentPacks()) { - lua::emit_event(L, pack.id + ".worldquit"); + lua::emit_event(L, pack.id + ":.worldquit"); } + scripting::level = nullptr; + scripting::content = nullptr; + scripting::indices = nullptr; + scripting::blocks = nullptr; + scripting::controller = nullptr; +} +void scripting::cleanup() { + auto L = lua::get_main_state(); lua::getglobal(L, "pack"); - for (auto& pack : scripting::engine->getContentPacks()) { + for (auto& pack : scripting::engine->getAllContentPacks()) { lua::getfield(L, "unload"); lua::pushstring(L, pack.id); lua::call_nothrow(L, 1); @@ -199,11 +207,6 @@ void scripting::on_world_quit() { if (lua::getglobal(L, "__scripts_cleanup")) { lua::call_nothrow(L, 0); } - scripting::level = nullptr; - scripting::content = nullptr; - scripting::indices = nullptr; - scripting::blocks = nullptr; - scripting::controller = nullptr; } void scripting::on_blocks_tick(const Block& block, int tps) { @@ -248,7 +251,7 @@ void scripting::on_block_placed( if (pack->worldfuncsset.onblockplaced) { lua::emit_event( lua::get_main_state(), - packid + ".blockplaced", + packid + ":.blockplaced", world_event_args ); } @@ -280,7 +283,7 @@ void scripting::on_block_broken( if (pack->worldfuncsset.onblockbroken) { lua::emit_event( lua::get_main_state(), - packid + ".blockbroken", + packid + ":.blockbroken", world_event_args ); } @@ -613,7 +616,7 @@ bool scripting::register_event( if (lua::getfield(L, name)) { lua::pop(L); lua::getglobal(L, "events"); - lua::getfield(L, "on"); + lua::getfield(L, "reset"); lua::pushstring(L, id); lua::getfield(L, name, -4); lua::call_nothrow(L, 2); @@ -686,14 +689,14 @@ void scripting::load_world_script( int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "world", file)); register_event(env, "init", prefix + ".init"); - register_event(env, "on_world_open", prefix + ".worldopen"); - register_event(env, "on_world_tick", prefix + ".worldtick"); - register_event(env, "on_world_save", prefix + ".worldsave"); - register_event(env, "on_world_quit", prefix + ".worldquit"); + register_event(env, "on_world_open", prefix + ":.worldopen"); + register_event(env, "on_world_tick", prefix + ":.worldtick"); + register_event(env, "on_world_save", prefix + ":.worldsave"); + register_event(env, "on_world_quit", prefix + ":.worldquit"); funcsset.onblockplaced = - register_event(env, "on_block_placed", prefix + ".blockplaced"); + register_event(env, "on_block_placed", prefix + ":.blockplaced"); funcsset.onblockbroken = - register_event(env, "on_block_broken", prefix + ".blockbroken"); + register_event(env, "on_block_broken", prefix + ":.blockbroken"); } void scripting::load_layout_script( @@ -703,6 +706,7 @@ void scripting::load_layout_script( uidocscript& script ) { int env = *senv; + lua::pop(lua::get_main_state(), load_script(env, "layout", file)); script.onopen = register_event(env, "on_open", prefix + ".open"); script.onprogress = diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index a34c21cf..af9bb4c2 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -62,6 +62,7 @@ namespace scripting { void on_world_tick(); void on_world_save(); void on_world_quit(); + void cleanup(); void on_blocks_tick(const Block& block, int tps); void update_block(const Block& block, int x, int y, int z); void random_update_block(const Block& block, int x, int y, int z); diff --git a/src/logic/scripting/scripting_hud.cpp b/src/logic/scripting/scripting_hud.cpp index a711453c..a8856837 100644 --- a/src/logic/scripting/scripting_hud.cpp +++ b/src/logic/scripting/scripting_hud.cpp @@ -22,7 +22,7 @@ void scripting::on_frontend_init(Hud* hud) { for (auto& pack : engine->getContentPacks()) { lua::emit_event( lua::get_main_state(), - pack.id + ".hudopen", + pack.id + ":.hudopen", [&](lua::State* L) { return lua::pushinteger(L, hud->getPlayer()->getId()); } @@ -34,7 +34,7 @@ void scripting::on_frontend_render() { for (auto& pack : engine->getContentPacks()) { lua::emit_event( lua::get_main_state(), - pack.id + ".hudrender", + pack.id + ":.hudrender", [&](lua::State* L) { return 0; } ); } @@ -44,7 +44,7 @@ void scripting::on_frontend_close() { for (auto& pack : engine->getContentPacks()) { lua::emit_event( lua::get_main_state(), - pack.id + ".hudclose", + pack.id + ":.hudclose", [&](lua::State* L) { return lua::pushinteger(L, hud->getPlayer()->getId()); } @@ -62,8 +62,8 @@ void scripting::load_hud_script( lua::execute(lua::get_main_state(), env, src, file.u8string()); - register_event(env, "init", packid + ".init"); - register_event(env, "on_hud_open", packid + ".hudopen"); - register_event(env, "on_hud_render", packid + ".hudrender"); - register_event(env, "on_hud_close", packid + ".hudclose"); + register_event(env, "init", packid + ":.init"); + register_event(env, "on_hud_open", packid + ":.hudopen"); + register_event(env, "on_hud_render", packid + ":.hudrender"); + register_event(env, "on_hud_close", packid + ":.hudclose"); } diff --git a/src/maths/Heightmap.cpp b/src/maths/Heightmap.cpp index 8db33f6b..ab6ad153 100644 --- a/src/maths/Heightmap.cpp +++ b/src/maths/Heightmap.cpp @@ -5,8 +5,15 @@ #include #include -static inline float smootherstep(float x) { - return glm::smoothstep(std::floor(x), std::floor(x)+1, x); +std::optional InterpolationType_from(std::string_view str) { + if (str == "nearest") { + return InterpolationType::NEAREST; + } else if (str == "linear") { + return InterpolationType::LINEAR; + } else if (str == "cubic") { + return InterpolationType::CUBIC; + } + return std::nullopt; } static inline float sample_at( @@ -17,6 +24,27 @@ static inline float sample_at( return buffer[y*width+x]; } +static inline float sample_at( + const float* buffer, + uint width, uint height, + uint x, uint y +) { + return buffer[(y >= height ? height-1 : y)*width+(x >= width ? width-1 : x)]; +} + +static inline float interpolate_cubic(float p[4], float x) { + return p[1] + 0.5 * x*(p[2] - p[0] + x*(2.0*p[0] - 5.0*p[1] + 4.0*p[2] - + p[3] + x*(3.0*(p[1] - p[2]) + p[3] - p[0]))); +} + +static inline float interpolate_bicubic(float p[4][4], float x, float y) { + float q[4]; + for (int i = 0; i < 4; i++) { + q[i] = interpolate_cubic(p[i], y); + } + return interpolate_cubic(q, x); +} + static inline float sample_at( const float* buffer, uint width, uint height, @@ -50,7 +78,17 @@ static inline float sample_at( return a00 + a10*tx + a01*ty + a11*tx*ty; } - // TODO: implement CUBIC (Bicubic) interpolation + case InterpolationType::CUBIC: { + float p[4][4]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + p[i][j] = sample_at( + buffer, width, height, ix + j - 1, iy + i - 1 + ); + } + } + return interpolate_bicubic(p, ty, tx); + } default: throw std::runtime_error("interpolation type is not implemented"); } diff --git a/src/maths/Heightmap.hpp b/src/maths/Heightmap.hpp index 5f903756..37f5130d 100644 --- a/src/maths/Heightmap.hpp +++ b/src/maths/Heightmap.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "typedefs.hpp" #include "maths/Heightmap.hpp" @@ -12,6 +13,8 @@ enum class InterpolationType { CUBIC, }; +std::optional InterpolationType_from(std::string_view str); + class Heightmap { std::vector buffer; uint width, height; diff --git a/src/objects/EntityDef.cpp b/src/objects/EntityDef.cpp index 8fa9c129..4cc85871 100644 --- a/src/objects/EntityDef.cpp +++ b/src/objects/EntityDef.cpp @@ -1,4 +1,6 @@ #include "EntityDef.hpp" + + void EntityDef::cloneTo(EntityDef& dst) { dst.components = components; dst.bodyType = bodyType; @@ -8,5 +10,4 @@ void EntityDef::cloneTo(EntityDef& dst) { dst.skeletonName = skeletonName; dst.blocking = blocking; dst.save = save; - -} \ No newline at end of file +} diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index dec61730..53c69c95 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -45,7 +45,7 @@ struct EntityDef { } skeleton; struct { bool velocity = true; - bool settings = false; + bool settings = true; } body; } save {}; diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index d0e19c85..5b925f58 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -40,11 +40,11 @@ Player::Player( position(position), inventory(std::move(inv)), eid(eid), - camera(level->getCamera("core:first-person")), + fpCamera(level->getCamera("core:first-person")), spCamera(level->getCamera("core:third-person-front")), tpCamera(level->getCamera("core:third-person-back")), - currentCamera(camera) { - camera->setFov(glm::radians(90.0f)); + currentCamera(fpCamera) { + fpCamera->setFov(glm::radians(90.0f)); spCamera->setFov(glm::radians(90.0f)); tpCamera->setFov(glm::radians(90.0f)); } @@ -93,16 +93,16 @@ void Player::updateInput(PlayerInput& input, float delta) { glm::vec3 dir(0, 0, 0); if (input.moveForward) { - dir += camera->dir; + dir += fpCamera->dir; } if (input.moveBack) { - dir -= camera->dir; + dir -= fpCamera->dir; } if (input.moveRight) { - dir += camera->right; + dir += fpCamera->right; } if (input.moveLeft) { - dir -= camera->right; + dir -= fpCamera->right; } if (glm::length(dir) > 0.0f) { dir = glm::normalize(dir); @@ -166,7 +166,7 @@ void Player::postUpdate() { auto& skeleton = entity->getSkeleton(); - skeleton.visible = currentCamera != camera; + skeleton.visible = currentCamera != fpCamera; auto body = skeleton.config->find("body"); auto head = skeleton.config->find("head"); @@ -252,7 +252,7 @@ entityid_t Player::getSelectedEntity() const { return selectedEid; } -std::shared_ptr Player::getInventory() const { +const std::shared_ptr& Player::getInventory() const { return inventory; } @@ -289,7 +289,7 @@ void Player::deserialize(const dv::value& src) { const auto& posarr = src["position"]; dv::get_vec(posarr, position); - camera->position = position; + fpCamera->position = position; const auto& rotarr = src["rotation"]; dv::get_vec(rotarr, cam); diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index c2a923e9..985aba55 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -52,7 +52,7 @@ class Player : public Object, public Serializable { entityid_t eid; entityid_t selectedEid; public: - std::shared_ptr camera, spCamera, tpCamera; + std::shared_ptr fpCamera, spCamera, tpCamera; std::shared_ptr currentCamera; bool debug = false; glm::vec3 cam {}; @@ -91,7 +91,7 @@ public: entityid_t getSelectedEntity() const; - std::shared_ptr getInventory() const; + const std::shared_ptr& getInventory() const; glm::vec3 getPosition() const { return position; diff --git a/src/settings.hpp b/src/settings.hpp index 8d1abdbf..8343ee68 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -29,6 +29,8 @@ struct DisplaySettings { IntegerSetting samples {0}; /// @brief Framerate limit IntegerSetting framerate {-1, -1, 120}; + /// @brief Limit framerate when window is iconified + FlagSetting limitFpsIconified {false}; }; struct ChunksSettings { diff --git a/src/util/Buffer.hpp b/src/util/Buffer.hpp index f7bf3ff6..403fa4d8 100644 --- a/src/util/Buffer.hpp +++ b/src/util/Buffer.hpp @@ -37,6 +37,8 @@ namespace util { Buffer(std::nullptr_t) noexcept : ptr(nullptr), length(0) {} + Buffer& operator=(Buffer&&) = default; + inline bool operator==(std::nullptr_t) const noexcept { return ptr == nullptr; } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index b749718d..cf16691a 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -5,14 +5,14 @@ #include #include #include +#include +#include "stringutil.hpp" #include "typedefs.hpp" #ifdef _WIN32 #include -#include "stringutil.hpp" - void platform::configure_encoding() { // set utf-8 encoding to console output SetConsoleOutputCP(CP_UTF8); @@ -36,6 +36,26 @@ std::string platform::detect_locale() { .substr(0, 5); } +void platform::sleep(size_t millis) { + // Uses implementation from the SFML library + // https://github.com/SFML/SFML/blob/master/src/SFML/System/Win32/SleepImpl.cpp + + // Get the minimum supported timer resolution on this system + static const UINT periodMin = []{ + TIMECAPS tc; + timeGetDevCaps(&tc, sizeof(TIMECAPS)); + return tc.wPeriodMin; + }(); + + // Set the timer resolution to the minimum for the Sleep call + timeBeginPeriod(periodMin); + + // Wait... + Sleep(static_cast(millis)); + + // Reset the timer resolution back to the system default + timeEndPeriod(periodMin); +} #else void platform::configure_encoding() { @@ -50,4 +70,21 @@ std::string platform::detect_locale() { return preferredLocaleName.substr(0, 5); } +void platform::sleep(size_t millis) { + std::this_thread::sleep_for(std::chrono::milliseconds(millis)); +} #endif + +void platform::open_folder(const std::filesystem::path& folder) { + if (!std::filesystem::is_directory(folder)) { + return; + } +#ifdef __APPLE__ + auto cmd = "open " + util::quote(folder.u8string()); +#elif defined(_WIN32) + auto cmd = "start explorer " + util::quote(folder.u8string()); +#else + auto cmd = "xdg-open " + util::quote(folder.u8string()); +#endif + system(cmd.c_str()); +} \ No newline at end of file diff --git a/src/util/platform.hpp b/src/util/platform.hpp index a620acad..1283bcfe 100644 --- a/src/util/platform.hpp +++ b/src/util/platform.hpp @@ -1,9 +1,15 @@ #pragma once #include +#include namespace platform { void configure_encoding(); - // @return environment locale in ISO format ll_CC + /// @return environment locale in ISO format ll_CC std::string detect_locale(); + /// @brief Open folder using system file manager asynchronously + /// @param folder target folder + void open_folder(const std::filesystem::path& folder); + /// Makes the current thread sleep for the specified amount of milliseconds. + void sleep(size_t millis); } diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index c104bd5e..ca7e8e46 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -11,7 +11,9 @@ std::string util::escape(const std::string& s) { std::stringstream ss; ss << '"'; - for (char c : s) { + size_t pos = 0; + while (pos < s.length()) { + char c = s[pos]; switch (c) { case '\n': ss << "\\n"; @@ -35,6 +37,13 @@ std::string util::escape(const std::string& s) { ss << "\\\\"; break; default: + if (c & 0x80) { + uint cpsize; + int codepoint = decode_utf8(cpsize, s.data() + pos); + pos += cpsize-1; + ss << "\\u" << std::hex << codepoint; + break; + } if (c < ' ') { ss << "\\" << std::oct << uint(ubyte(c)); break; @@ -42,6 +51,7 @@ std::string util::escape(const std::string& s) { ss << c; break; } + pos++; } ss << '"'; return ss.str(); @@ -128,7 +138,7 @@ inline uint utf8_len(ubyte cp) { if ((cp & 0xF8) == 0xF0) { return 4; } - return 0; + throw std::runtime_error("utf8 decode error"); } uint32_t util::decode_utf8(uint& size, const char* chr) { @@ -156,6 +166,16 @@ size_t util::crop_utf8(std::string_view s, size_t maxSize) { return pos; } +size_t util::length_utf8(std::string_view s) { + size_t length = 0; + size_t pos = 0; + while (pos < s.length()) { + pos += utf8_len(s[pos]); + length++; + } + return length; +} + template std::string xstr2str_utf8(const std::basic_string& xs) { std::vector chars; diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index c7bc2fb5..f584a53c 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -44,6 +44,11 @@ namespace util { /// @param maxSize max encoded string length after crop /// @return cropped string size (less or equal to maxSize) size_t crop_utf8(std::string_view s, size_t maxSize); + + /// @brief Measure utf8-encoded string length + /// @param s source encoded string + /// @return unicode string length (number of codepoints) + size_t length_utf8(std::string_view s); bool is_integer(const std::string& text); bool is_integer(const std::wstring& text); diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index d09cbd99..963a0928 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -141,6 +141,7 @@ void Block::cloneTo(Block& dst) { dst.uiLayout = uiLayout; dst.inventorySize = inventorySize; dst.tickInterval = tickInterval; + dst.overlayTexture = overlayTexture; } static std::set> RESERVED_BLOCK_FIELDS { diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index e9b91664..2d2cdf90 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "maths/UVRegion.hpp" #include "maths/aabb.hpp" @@ -111,7 +112,7 @@ public: std::string caption; /// @brief Textures set applied to block sides - std::string textureFaces[6]; // -x,x, -y,y, -z,z + std::array textureFaces; // -x,x, -y,y, -z,z std::vector modelTextures = {}; std::vector modelBoxes = {}; @@ -184,6 +185,9 @@ public: /// @brief Block will be used instead of this if generated on surface std::string surfaceReplacement = name; + /// @brief Texture will be shown on screen if camera is inside of the block + std::string overlayTexture; + /// @brief Default block layout will be used by hud.open_block(...) std::string uiLayout = name; diff --git a/src/window/Camera.cpp b/src/window/Camera.cpp index 5e9dd36a..def9ff7c 100644 --- a/src/window/Camera.cpp +++ b/src/window/Camera.cpp @@ -29,21 +29,21 @@ void Camera::rotate(float x, float y, float z) { updateVectors(); } -glm::mat4 Camera::getProjection() { +glm::mat4 Camera::getProjection() const { constexpr float epsilon = 1e-6f; // 0.000001 float aspect_ratio = this->aspect; if (std::fabs(aspect_ratio) < epsilon) { aspect_ratio = (float)Window::width / (float)Window::height; } if (perspective) - return glm::perspective(fov * zoom, aspect_ratio, 0.05f, 1500.0f); + return glm::perspective(fov * zoom, aspect_ratio, near, far); else if (flipped) return glm::ortho(0.0f, fov * aspect_ratio, fov, 0.0f); else return glm::ortho(0.0f, fov * aspect_ratio, 0.0f, fov); } -glm::mat4 Camera::getView(bool pos) { +glm::mat4 Camera::getView(bool pos) const { glm::vec3 camera_pos = this->position; if (!pos) { camera_pos = glm::vec3(0.0f); @@ -55,7 +55,7 @@ glm::mat4 Camera::getView(bool pos) { } } -glm::mat4 Camera::getProjView(bool pos) { +glm::mat4 Camera::getProjView(bool pos) const { return getProjection() * getView(pos); } diff --git a/src/window/Camera.hpp b/src/window/Camera.hpp index 639a27df..1c2ecd60 100644 --- a/src/window/Camera.hpp +++ b/src/window/Camera.hpp @@ -18,6 +18,8 @@ public: bool perspective = true; bool flipped = false; float aspect = 0.0f; + float near = 0.05f; + float far = 1500.0f; Camera() { updateVectors(); @@ -27,9 +29,9 @@ public: void updateVectors(); void rotate(float x, float y, float z); - glm::mat4 getProjection(); - glm::mat4 getView(bool position = true); - glm::mat4 getProjView(bool position = true); + glm::mat4 getProjection() const; + glm::mat4 getView(bool position = true) const; + glm::mat4 getProjView(bool position = true) const; void setFov(float fov); float getFov() const; diff --git a/src/window/Events.cpp b/src/window/Events.cpp index 092e4a0d..f63431cf 100644 --- a/src/window/Events.cpp +++ b/src/window/Events.cpp @@ -78,6 +78,10 @@ void Events::pollEvents() { for (auto& entry : bindings) { auto& binding = entry.second; + if (!binding.enable) { + binding.state = false; + continue; + } binding.justChange = false; bool newstate = false; @@ -106,9 +110,9 @@ void Events::pollEvents() { } Binding& Events::getBinding(const std::string& name) { - auto found = bindings.find(name); + const auto found = bindings.find(name); if (found == bindings.end()) { - throw std::runtime_error("binding '" + name + "' does not exists"); + throw std::runtime_error("binding '" + name + "' does not exist"); } return found->second; } @@ -126,6 +130,10 @@ void Events::bind(const std::string& name, inputtype type, int code) { } void Events::rebind(const std::string& name, inputtype type, int code) { + const auto& found = bindings.find(name); + if (found == bindings.end()) { + throw std::runtime_error("binding '" + name + "' does not exist"); + } bindings[name] = Binding(type, code); } @@ -193,7 +201,8 @@ std::string Events::writeBindings() { } void Events::loadBindings( - const std::string& filename, const std::string& source + const std::string& filename, const std::string& source, + BindType bindType ) { auto map = toml::parse(filename, source); for (auto& [sectionName, section] : map.asObject()) { @@ -214,7 +223,19 @@ void Events::loadBindings( << util::quote(key) << ")"; continue; } - Events::bind(key, type, code); + if (bindType == BindType::BIND) { + Events::bind(key, type, code); + } else if (bindType == BindType::REBIND) { + Events::rebind(key, type, code); + } + } } } + +void Events::enableBindings() { + for (auto& entry : bindings) { + auto& binding = entry.second; + binding.enable = true; + } +} diff --git a/src/window/Events.hpp b/src/window/Events.hpp index 230145a9..8155bb2b 100644 --- a/src/window/Events.hpp +++ b/src/window/Events.hpp @@ -9,6 +9,11 @@ inline constexpr short KEYS_BUFFER_SIZE = 1036; +enum class BindType { + BIND = 0, + REBIND = 1 +}; + class Events { static bool keys[KEYS_BUFFER_SIZE]; static uint frames[KEYS_BUFFER_SIZE]; @@ -52,6 +57,8 @@ public: static std::string writeBindings(); static void loadBindings( - const std::string& filename, const std::string& source + const std::string& filename, const std::string& source, + BindType bindType ); + static void enableBindings(); }; diff --git a/src/window/Window.cpp b/src/window/Window.cpp index c77f0d4c..2de948a3 100644 --- a/src/window/Window.cpp +++ b/src/window/Window.cpp @@ -14,6 +14,8 @@ #include "util/ObjectsKeeper.hpp" #include "Events.hpp" +#include "util/platform.hpp" + static debug::Logger logger("window"); GLFWwindow* Window::window = nullptr; @@ -357,11 +359,14 @@ bool Window::isFullscreen() { void Window::swapBuffers() { glfwSwapBuffers(window); Window::resetScissor(); - double currentTime = time(); - if (framerate > 0 && currentTime - prevSwap < (1.0 / framerate)) { - std::this_thread::sleep_for(std::chrono::milliseconds(static_cast( - (1.0 / framerate - (currentTime - prevSwap)) * 1000 - ))); + if (framerate > 0) { + auto elapsedTime = time() - prevSwap; + auto frameTime = 1.0 / framerate; + if (elapsedTime < frameTime) { + platform::sleep( + static_cast((frameTime - elapsedTime) * 1000) + ); + } } prevSwap = time(); } diff --git a/src/window/input.hpp b/src/window/input.hpp index a90c8fe0..06ed1511 100644 --- a/src/window/input.hpp +++ b/src/window/input.hpp @@ -137,6 +137,7 @@ struct Binding { int code; bool state = false; bool justChange = false; + bool enable = true; Binding() = default; Binding(inputtype type, int code) : type(type), code(code) { diff --git a/src/world/generator/GeneratorDef.hpp b/src/world/generator/GeneratorDef.hpp index f3abd893..b21a2425 100644 --- a/src/world/generator/GeneratorDef.hpp +++ b/src/world/generator/GeneratorDef.hpp @@ -209,6 +209,12 @@ struct GeneratorDef { /// @brief Heightmap blocks per dot uint heightsBPD = 4; + /// @brief Biome parameter maps interpolation method + InterpolationType biomesInterpolation = InterpolationType::LINEAR; + + /// @brief Height maps interpolation method + InterpolationType heightsInterpolation = InterpolationType::LINEAR; + /// @brief Number of chunks must be generated before and after wide /// structures placement triggered uint wideStructsChunksRadius = 3; diff --git a/src/world/generator/VoxelFragment.cpp b/src/world/generator/VoxelFragment.cpp index 832c1109..2d287b0b 100644 --- a/src/world/generator/VoxelFragment.cpp +++ b/src/world/generator/VoxelFragment.cpp @@ -6,6 +6,7 @@ #include "data/dv_util.hpp" #include "content/Content.hpp" +#include "voxels/Chunks.hpp" #include "voxels/Block.hpp" #include "voxels/ChunksStorage.hpp" #include "voxels/VoxelsVolume.hpp" @@ -20,7 +21,7 @@ std::unique_ptr VoxelFragment::create( bool entities ) { auto start = glm::min(a, b); - auto size = glm::abs(a - b); + auto size = glm::abs(a - b) + 1; if (crop) { VoxelsVolume volume(size.x, size.y, size.z); @@ -168,6 +169,29 @@ void VoxelFragment::prepare(const Content& content) { } } +void VoxelFragment::place( + Chunks& chunks, const glm::ivec3& offset, ubyte rotation +) { + auto& structVoxels = getRuntimeVoxels(); + for (int y = 0; y < size.y; y++) { + int sy = y + offset.y; + if (sy < 0 || sy >= CHUNK_H) { + continue; + } + for (int z = 0; z < size.z; z++) { + int sz = z + offset.z; + for (int x = 0; x < size.x; x++) { + int sx = x + offset.x; + const auto& structVoxel = + structVoxels[vox_index(x, y, z, size.x, size.z)]; + if (structVoxel.id) { + chunks.set(sx, sy, sz, structVoxel.id, structVoxel.state); + } + } + } + } +} + std::unique_ptr VoxelFragment::rotated(const Content& content) const { std::vector newVoxels(voxels.size()); diff --git a/src/world/generator/VoxelFragment.hpp b/src/world/generator/VoxelFragment.hpp index 95a5375b..701cac0e 100644 --- a/src/world/generator/VoxelFragment.hpp +++ b/src/world/generator/VoxelFragment.hpp @@ -10,6 +10,7 @@ inline constexpr int STRUCTURE_FORMAT_VERSION = 1; class Level; class Content; +class Chunks; class VoxelFragment : public Serializable { glm::ivec3 size; @@ -41,6 +42,11 @@ public: /// @param content world content void prepare(const Content& content); + /// @brief Place fragment to the world + /// @param offset target location + /// @param rotation rotation index + void place(Chunks& chunks, const glm::ivec3& offset, ubyte rotation); + /// @brief Create structure copy rotated 90 deg. clockwise std::unique_ptr rotated(const Content& content) const; diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index f585e71b..c0084df4 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -307,13 +307,13 @@ void WorldGenerator::generateBiomes( copy->resize( floordiv(CHUNK_W, def.heightsBPD) + 1, floordiv(CHUNK_D, def.heightsBPD) + 1, - InterpolationType::LINEAR + def.heightsInterpolation ); prototype.heightmapInputs.push_back(std::move(copy)); } for (const auto& map : biomeParams) { map->resize( - CHUNK_W + bpd, CHUNK_D + bpd, InterpolationType::LINEAR + CHUNK_W + bpd, CHUNK_D + bpd, def.biomesInterpolation ); map->crop(0, 0, CHUNK_W, CHUNK_D); } @@ -345,7 +345,7 @@ void WorldGenerator::generateHeightmap( ); prototype.heightmap->clamp(); prototype.heightmap->resize( - CHUNK_W + bpd, CHUNK_D + bpd, InterpolationType::LINEAR + CHUNK_W + bpd, CHUNK_D + bpd, def.heightsInterpolation ); prototype.heightmap->crop(0, 0, CHUNK_W, CHUNK_D); prototype.level = ChunkPrototypeLevel::HEIGHTMAP; diff --git a/test/coders/vec3.cpp b/test/coders/vec3.cpp new file mode 100644 index 00000000..cb699bc3 --- /dev/null +++ b/test/coders/vec3.cpp @@ -0,0 +1,12 @@ +#include + +#include "coders/vec3.hpp" +#include "files/files.hpp" + +TEST(VEC3, Decode) { + auto file = std::filesystem::u8path( + "../res/models/block.vec3" + ); + auto bytes = files::read_bytes_buffer(file); + auto model = vec3::load(file.u8string(), bytes); +}