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