diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 00000000..68f6acc2 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,44 @@ +name: Macos Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build-dmg: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'true' + + - name: Install dependencies from brew + run: | + brew install glfw3 glew libpng openal-soft luajit libvorbis + + - name: Install specific version of GLM + run: | + curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/5c7655a866646aa4b857c002b8ae5465b9d26f65/Formula/g/glm.rb + brew install --formula glm.rb + + - name: Configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1 + + - name: Build + run: cmake --build build -t install + + - name: Create DMG + run: | + mkdir VoxelEngineDmgContent + cp -r build/res VoxelEngineDmgContent/ + cp -r build/VoxelEngine VoxelEngineDmgContent/ + hdiutil create VoxelEngineMacApp.dmg -volname "VoxelEngine" -srcfolder VoxelEngineDmgContent -ov -format UDZO + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: VoxelEngineMacOs + path: VoxelEngineMacApp.dmg \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000..46fe7122 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,48 @@ +name: Windows Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build-windows: + + strategy: + matrix: + include: + - os: windows-latest + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'true' + + - name: Set up vcpkg + run: | + git clone https://github.com/microsoft/vcpkg.git + cd vcpkg + .\bootstrap-vcpkg.bat + .\vcpkg integrate install + cd .. + - name: Configure and build project with CMake and vcpkg + run: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON .. + Remove-Item -Path CMakeFiles -Recurse -Force + cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON .. + cmake --build . --config Release + - name: Package for Windows + run: | + mkdir packaged + cp -r build/* packaged/ + working-directory: ${{ github.workspace }} + + - uses: actions/upload-artifact@v2 + with: + name: Windows-Build + path: 'packaged/Release/*' diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b4e4bfd..2ec2faab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,9 @@ else() -Wformat-nonliteral -Wcast-align -Wpointer-arith -Wundef -Wwrite-strings -Wno-unused-parameter) + if (CMAKE_BUILD_TYPE MATCHES "Debug") + target_compile_options(${PROJECT_NAME} PRIVATE -Og) + endif() endif() if(VOXELENGINE_BUILD_WINDOWS_VCPKG AND WIN32) @@ -98,6 +101,18 @@ if (WIN32) set(VORBISLIB vorbis vorbisfile) # not tested add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw) endif() +elseif(APPLE) + find_package(PkgConfig) + pkg_check_modules(LUAJIT REQUIRED luajit) + pkg_check_modules(VORBIS REQUIRED vorbis vorbisfile) + set(LUA_INCLUDE_DIR "/opt/homebrew/include/luajit-2.1") + set(LUA_LIBRARIES "/opt/homebrew/lib/libluajit-5.1.a") + message(STATUS "LUA Libraries: ${LUA_LIBRARIES}") + message(STATUS "LUA Include Dir: ${LUA_INCLUDE_DIR}") + find_package(PNG REQUIRED) + set(PNGLIB PNG::PNG) + set(VORBISLIB ${VORBIS_LDFLAGS}) + message(STATUS "Vorbis Lib: ${VORBIS_LDFLAGS}") else() find_package(PkgConfig) pkg_check_modules(LUAJIT REQUIRED luajit) diff --git a/doc/ru/8.Скриптинг.md b/doc/ru/8.Скриптинг.md index e68ea1a4..f5ba82c2 100644 --- a/doc/ru/8.Скриптинг.md +++ b/doc/ru/8.Скриптинг.md @@ -2,11 +2,7 @@ В качестве языка сценариев используется LuaJIT -## Функции, доступные в скриптах - ```lua -load_script("контентпак:scripts/имя_скрипта.lua") -- загружает скрипт, если ещё не загружен -load_script("контентпак:scripts/имя_скрипта.lua", true) -- перезагружает скрипт require "контентпак:имя_модуля" -- загружает lua модуль из папки modules (расширение не указывается) ``` @@ -77,8 +73,23 @@ player.set_noclip(bool) Геттер и сеттер noclip режима (выключенная коллизия игрока) +```python +player.get_selected_block(playerid: int) -> x,y,z +``` + +Возвращает координаты выделенного блока, либо nil + ## Библиотека world +```python +world.get_list() -> массив таблиц { + name: str, + icon: str +} +``` + +Возвращает информацию о мирах: название и предпросмотр (автоматически загружаемая текстура). + ```python world.get_day_time() -> number ``` @@ -103,6 +114,12 @@ world.get_seed() -> int Возвращает зерно мира. +```python +world.exists() -> bool +``` + +Проверяет существование мира по имени. + ## Библиотека pack ```python @@ -123,6 +140,38 @@ pack.get_installed() -> массив строк Возращает id всех установленных в мире контент-паков +```python +pack.get_available() -> массив строк +``` + +Возвращает id всех доступных, но не установленных в мире контент-паков + +```python +pack.get_base_packs() -> массив строк +``` + +Возвращает id всех базовых паков (неудаляемых) + +```python +pack.get_info(packid: str) -> { + id: str, + title: str, + creator: str, + description: str, + version: str, + icon: str, + dependencies: опциональный массив строк +} +``` + +Возвращает информацию о паке (не обязательно установленном). +- icon - название текстуры предпросмотра (загружается автоматически) +- dependencies - строки в формате `{lvl}{id}`, где lvl: + - `!` - required + - `?` - optional + - `~` - weak + например `!teal` + ## Библиотека gui Библиотека содержит функции для доступа к свойствам UI элементов. Вместо gui следует использовать объектную обертку, предоставляющую доступ к свойствам через мета-методы __index, __newindex: @@ -134,6 +183,33 @@ indentory_doc.some_button.text = "new text" В скрипте макета `layouts/файл_макета.xml` - `layouts/файл_макета.xml.lua` уже доступна переменная **document** содержащая объект класса Document +```python +gui.str(text: str, context: str) -> str +``` + +Возращает переведенный текст. + +```python +gui.get_viewport() -> {int, int} +``` + +Возвращает размер главного контейнера (окна). + +```python +gui.get_env(document: str) -> table +``` + +Возвращает окружение (таблица глобальных переменных) указанного документа. + +```python +get_locales_info() -> таблица таблиц где + ключ - id локали в формате isolangcode_ISOCOUNTRYCODE + значение - таблица { + name: str # название локали на её языке + } +``` + +Возвращает информацию о всех загруженных локалях (res/texts/\*). ## Библиотека inventory Библиотека функций для работы с инвентарем. @@ -211,6 +287,18 @@ block.index(name: str) -> int Возвращает числовой id блока, принимая в качестве агрумента строковый +```python +block.material(blockid: int) -> str +``` + +Возвращает id материала блока. + +```python +block.caption(blockid: int) -> str +``` + +Возвращает название блока, отображаемое в интерфейсе. + ```python block.get(x: int, y: int, z: int) -> int ``` @@ -373,206 +461,27 @@ hud.close(layoutid: str) hud.get_block_inventory() -> int ``` -Получить ID инвентаря открытого блока или 0 - -## События блоков - -```lua -function on_placed(x, y, z, playerid) -``` - -Вызывается после установки блока игроком - -```lua -function on_broken(x, y, z, playerid) -``` - -Вызывается после разрушения блока игроком - -```lua -function on_interact(x, y, z, playerid) -> bool -``` - -Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true` - -```lua -function on_update(x, y, z) -``` - -Вызывается при обновлении блока (если изменился соседний блок) - -```lua -function on_random_update(x, y, z) -``` - -Вызывается в случайные моменты времени (рост травы на блоках земли) - -```lua -function on_blocks_tick(tps: int) -``` - -Вызывается tps (20) раз в секунду - -## События предметов - -```lua -function on_use(playerid: int) -``` - -Вызывается при нажатии ПКМ не на блок. - -```lua -function on_use_on_block(x: int, y: int, z: int, playerid: int) -``` - -Вызывается при нажатии ПКМ на блок. Предотвращает установку блока, прописанного в `placing-block` если возвращает `true` - -```lua -function on_block_break_by(x: int, y: int, z: int, playerid: int) -``` - -Вызывается при нажатии ЛКМ на блок (в т.ч неразрушимый). Предотвращает разрушение блока, если возвращает `true` - -## События мира - -События мира для контент-пака прописываются в `scripts/world.lua` - -```lua -function on_world_open() -``` - -Вызывается при загрузке мира - -```lua -function on_world_save() -``` - -Вызывается перед сохранением мира - -```lua -function on_world_tick() -``` - -Вызывается 20 раз в секунду - -```lua -function on_world_quit() -``` - -Вызывается при выходе из мира (после сохранения) - -## События макета - -События прописываются в файле `layouts/имя_макета.xml.lua`. - -```lua -function on_open(invid: int, x: int, y: int, z: int) -``` - -Вызывается при добавлении элемента на экран. -При отсутствии привязки к инвентарю invid будет равен 0. -При отсутствии привязки к блоку x, y, z так же будут равны 0. - -```lua -function on_close(invid: int) -``` - -Вызывается при удалении элемента с экрана. - -## События HUD - -События связанные с игровым интерфейсом прописываются в файле `scripts/hud.lua` - -```lua -function on_hud_open(playerid: int) -``` - -Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы. - -```lua -function on_hud_close(playerid: int) -``` - -Вызывается при выходе из мира, перед его сохранением. - -## Библиотеки движка - -### file - -Библиотека функций для работы с файлами +Дает ID инвентаря открытого блока или 0 ```python -file.resolve(путь: str) -> str +hud.get_player() -> int ``` -Функция приводит запись `точка_входа:путь` (например `user:worlds/house1`) к обычному пути. (например `C://Users/user/.voxeng/worlds/house1`) - -> [!NOTE] -> Функцию не нужно использовать в сочетании с другими функциями из библиотеки, так как они делают это автоматически - -Возвращаемый путь не является каноническим и может быть как абсолютным, так и относительным. +Дает ID игрока, к которому привязан пользовательский интерфейс ```python -file.read(путь: str) -> str +hud.pause() ``` -Читает весь текстовый файл и возвращает в виде строки +Открывает меню паузы ```python -file.read_bytes(путь: str) -> array of integers +hud.resume() ``` -Читает файл в массив байт. +Закрывает меню паузы. -```python -file.write(путь: str, текст: str) -> nil -``` - -Записывает текст в файл (с перезаписью) - -```python -file.write_bytes(путь: str, data: array of integers) -``` - -Записывает массив байт в файл (с перезаписью) - -```python -file.length(путь: str) -> int -``` - -Возвращает размер файла в байтах, либо -1, если файл не найден - -```python -file.exists(путь: str) -> bool -``` - -Проверяет, существует ли по данному пути файл или директория - -```python -file.isfile(путь: str) -> bool -``` - -Проверяет, существует ли по данному пути файл - -```python -file.isdir(путь: str) -> bool -``` - -Проверяет, существует ли по данному пути директория - -```python -file.mkdir(путь: str) -> bool -``` - -Создает директорию. Возвращает true если была создана новая директория - -```python -file.mkdirs(путь: str) -> bool -``` - -Создает всю цепочку директорий. Возвращает true если были созданы директории. - -### time +## Библиотека time ```python time.uptime() -> float @@ -580,24 +489,8 @@ time.uptime() -> float Возвращает время с момента запуска движка в секундах -## Доступные модули - -### TOML сериализация/десериализация - -```lua -local toml = require "core:toml" - -local t = {a=53, b=42, s="test", sub={x=1, y=6}} -local s = toml.serialize(t) -print(s) -local t2 = toml.deserialize(s) -``` -вывод: -```toml -b = 42 -s = "test" -a = 53 -[sub] -y = 6 -x = 1 +```python +time.delta() -> float ``` + +Возвращает дельту времени (время прошедшее с предыдущего кадра) diff --git a/doc/ru/Консоль.md b/doc/ru/Консоль.md new file mode 100644 index 00000000..a279be61 --- /dev/null +++ b/doc/ru/Консоль.md @@ -0,0 +1,109 @@ +# Консоль + +Для работы с командным интерпретатором предоставляется библиотека **console** + +## Создание команд + +Для создания команды консоли используется следующая функция: + +```python +console.add_command(схема: str, исполнитель: function) +``` + +Схема имеет следующий синтаксис: + +``` +название позиционные параметры {именованные параметры} +``` + +Название может содержать: +- латинницу +- цифры (кроме первого символа) +- `.`, `_`, `-` + +Позиционные параметры разделяются пробелами и имеют следующий синтаксис: + +``` +название:тип (вариант 1) +название:тип=по-умолчанию (вариант 2) +название:тип~центральное-значение (вариант 3) +название:тип=по-умолчанию~центральное-значение (вариант 4) +``` + +Доступные типы: +- **int** - целое число +- **num** - дробное число +- **str** - строка +- **sel** - селектор (id объекта представленный целым числом) +- **enum** - перечисление + +На вариантах 3 и 4 показан оператор `~` позволяющий использовать относительные значения. *Центральное значение* - значение, относительно которого будет указываться пользовательское. Например позиция игрока. + +Относительный оператор работает только с числами (num или int) + +В качестве центральных значений могут указываться переменные, назначаемые через **console.set**. + +Пример: + +```python +x:num~pos.x +``` + +Переменные можно указывать и в качестве значений по-умолчанию, при использовании префикса `$`: + +```python +t:int=$time +``` + +Перечисления указывазываются в формате: + +```python +mode:[replace|destruct|none] +``` + +Либо через переменную: + +```python +mode:enum $modes +``` + +Селекторы указываются с префиксом `@`. На данный момент являются заглушкой, по причине отсутствия объектной модели. Следует делать опциональными и использовать переменные: + +```python +obj:sel=$obj.id # obj.id - id игрока +``` + +Именованные аргументы указываются в специальном блоке, ограниченном фигурными скобками `{ }` по той же схеме. + +Пример: + +```python +eval name:str="World" {greeting:str='Hello'} +``` + +## Примеры схем команд + +Схемы существующих команд можно найти в файле `res/script/stdcmd.lua`. + +Пример - команда **tp**: + +```python +tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z +``` + +Полный lua код создания команды: + +```lua +console.add_command( + "tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z", + "Teleport object", + function (args, kwargs) + player.set_pos(unpack(args)) + end +) +``` + +- В args передаются готовые значения позиционных аргументов. +- В kwargs передается таблица значений именованных аргументов. + +Проверку и приведение типов интерпретатор команд производит автоматически. diff --git a/doc/ru/Пользовательский-ввод.md b/doc/ru/Пользовательский-ввод.md new file mode 100644 index 00000000..e113b3b1 --- /dev/null +++ b/doc/ru/Пользовательский-ввод.md @@ -0,0 +1,59 @@ +# Пользовательский ввод + +Обработка нажатий клавиш и кнопок мыши обрабатываются через привязки (bindings), которые назначаются в паке, в файле `config/bindings.toml` в формате: + +```toml +packid.binding.name="inputtype:codename" +``` + +- packid - опционально, но желательно +- inputtype - key или mouse +- codename - имя клавиши или кнопки мыши (left/right/middle) + +## Имена клавиш + +- space, backspace, tab, enter, caps-lock, escape +- left-ctrl, left-shift, left-alt, left-super +- right-ctrl, right-shift, right-alt, right-super +- delete, home, end, insert, page-up, page-down +- left, right, down, up +- a..z +- 0..9 +- f1..f25 + +## Библиотека input + +```python +input.keycode(keyname: str) -> int +``` + +Возвращает код клавиши по имени, либо -1 + +```python +input.mousecode(mousename: str) -> int +``` + +Возвращает код кнопки мыши по имени, либо -1 + +```python +input.add_callback(bindname: str, callback: function) +``` + +Назначает функцию, которая будет вызываться при активации привязки. Пример: +```lua +input.add_callback("hud.inventory", function () + print("Inventory open key pressed") +end) +``` + +```python +input.get_mouse_pos() -> {int, int} +``` + +Возвращает позицию курсора на экране. + +```python +input.get_bindings() -> массив строк +``` + +Возвращает названия всех доступных привязок. diff --git a/doc/ru/События-движка.md b/doc/ru/События-движка.md new file mode 100644 index 00000000..ecfd2db9 --- /dev/null +++ b/doc/ru/События-движка.md @@ -0,0 +1,126 @@ +# События движка + +## События блоков + +Функции для обработки событий, прописываемые в скрипте блока. + +```lua +function on_placed(x, y, z, playerid) +``` + +Вызывается после установки блока игроком + +```lua +function on_broken(x, y, z, playerid) +``` + +Вызывается после разрушения блока игроком + +```lua +function on_interact(x, y, z, playerid) -> bool +``` + +Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true` + +```lua +function on_update(x, y, z) +``` + +Вызывается при обновлении блока (если изменился соседний блок) + +```lua +function on_random_update(x, y, z) +``` + +Вызывается в случайные моменты времени (рост травы на блоках земли) + +```lua +function on_blocks_tick(tps: int) +``` + +Вызывается tps (20) раз в секунду + +## События предметов + +Функции для обработки событий, прописываемые в скрипте предмета. + +```lua +function on_use(playerid: int) +``` + +Вызывается при нажатии ПКМ не на блок. + +```lua + +function on_use_on_block(x: int, y: int, z: int, playerid: int) + +``` + +Вызывается при нажатии ПКМ на блок. Предотвращает установку блока, прописанного в `placing-block` если возвращает `true` + +```lua +function on_block_break_by(x: int, y: int, z: int, playerid: int) +``` + +Вызывается при нажатии ЛКМ на блок (в т.ч неразрушимый). Предотвращает разрушение блока, если возвращает `true` + +## События мира + +События мира для контент-пака прописываются в `scripts/world.lua` + +```lua +function on_world_open() +``` + +Вызывается при загрузке мира + +```lua +function on_world_save() +``` + +Вызывается перед сохранением мира + +```lua +function on_world_tick() +``` + +Вызывается 20 раз в секунду + +```lua +function on_world_quit() +``` + +Вызывается при выходе из мира (после сохранения) +## События макета + +События прописываются в файле `layouts/имя_макета.xml.lua`. + +```lua +function on_open(invid: int, x: int, y: int, z: int) +``` + +Вызывается при добавлении элемента на экран. +- При отсутствии привязки к инвентарю invid будет равен 0. +- При отсутствии привязки к блоку x, y, z так же будут равны 0. + +```lua +function on_close(invid: int) +``` + +Вызывается при удалении элемента с экрана. + +## События HUD + +События связанные с игровым интерфейсом прописываются в файле `scripts/hud.lua` + +```lua +function on_hud_open(playerid: int) +``` + +Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы. + +```lua +function on_hud_close(playerid: int) +``` + +Вызывается при выходе из мира, перед его сохранением. diff --git a/doc/ru/Файловая-система-и-сериализация.md b/doc/ru/Файловая-система-и-сериализация.md new file mode 100644 index 00000000..ed350048 --- /dev/null +++ b/doc/ru/Файловая-система-и-сериализация.md @@ -0,0 +1,137 @@ +## Библиотека *file* + +Библиотека функций для работы с файлами + +```python +file.resolve(путь: str) -> str +``` + +Функция приводит запись `точка_входа:путь` (например `user:worlds/house1`) к обычному пути. (например `C://Users/user/.voxeng/worlds/house1`) + +> [!NOTE] +> Функцию не нужно использовать в сочетании с другими функциями из библиотеки, так как они делают это автоматически + +Возвращаемый путь не является каноническим и может быть как абсолютным, так и относительным. + +```python +file.read(путь: str) -> str +``` + +Читает весь текстовый файл и возвращает в виде строки + +```python +file.read_bytes(путь: str) -> array of integers +``` + +Читает файл в массив байт. + +```python +file.write(путь: str, текст: str) -> nil +``` + +Записывает текст в файл (с перезаписью) + +```python +file.write_bytes(путь: str, data: array of integers) +``` + +Записывает массив байт в файл (с перезаписью) + +```python +file.length(путь: str) -> int +``` + +Возвращает размер файла в байтах, либо -1, если файл не найден + +```python +file.exists(путь: str) -> bool +``` + +Проверяет, существует ли по данному пути файл или директория + +```python +file.isfile(путь: str) -> bool +``` + +Проверяет, существует ли по данному пути файл + +```python +file.isdir(путь: str) -> bool +``` + +Проверяет, существует ли по данному пути директория + +```python +file.mkdir(путь: str) -> bool +``` + +Создает директорию. Возвращает true если была создана новая директория + +```python +file.mkdirs(путь: str) -> bool +``` + +Создает всю цепочку директорий. Возвращает true если были созданы директории. + +```python +file.find(путь: str) -> str +``` + +Ищет файл от последнего пака до res. Путь указывается без префикса. Возвращает путь с нужным префиксом. Если файл не найден, возвращает nil. + +```python +file.remove(путь: str) -> bool +``` + +Удаляет файл. Возращает **true** если файл существовал. Бросает исключение при нарушении доступа. + +```python +file.remove_tree(путь: str) -> int +``` + +Рекурсивно удаляет файлы. Возвращает число удаленных файлов. + +## Библиотека json + +Библиотека содержит функции для сериализации и десериализации таблиц: + +```python +json.tostring(object: table, human_readable: bool=false) -> str +``` + +Сериализует объект в JSON строку. При значении второго параметра **true** будет использовано многострочное форматирование, удобное для чтения человеком, а не компактное, использующееся по-умолчанию. + +```python +json.parse(code: str) -> table +``` + +Парсит JSON строку в таблицу. + +## Библиотека toml + +Библиотека содержит функции для сериализации и десериализации таблиц: + +```python +toml.tostring(object: table) -> str +``` + +Сериализует объект в TOML строку. + +```python +toml.parse(code: str) -> table +``` + +Парсит TOML строку в таблицу. + +## Сохранение данных в мире + +При сохранении данных пака в мире следует использовать функцию +```python +pack.data_file(packid: str, filename: str) -> str +``` + +Функция возвращает путь к файлу данных по типу: `world:data/packid/filename` + +и создает недостающие директории в пути. + +При использовании путей не соответствующим `data/{packid}/...` возможна потеря данных при перезаписи мира. diff --git a/res/bindings.toml b/res/config/bindings.toml similarity index 94% rename from res/bindings.toml rename to res/config/bindings.toml index d0a7e1e9..e13be068 100644 --- a/res/bindings.toml +++ b/res/config/bindings.toml @@ -1,4 +1,5 @@ devtools.console="key:grave-accent" +chunks.reload="key:f5" movement.forward="key:w" movement.back="key:s" movement.left="key:a" diff --git a/res/config/builtins.list b/res/config/builtins.list new file mode 100644 index 00000000..df967b96 --- /dev/null +++ b/res/config/builtins.list @@ -0,0 +1 @@ +base diff --git a/res/content/base/scripts/grass_block.lua b/res/content/base/scripts/grass_block.lua index d92813c4..e58597cc 100644 --- a/res/content/base/scripts/grass_block.lua +++ b/res/content/base/scripts/grass_block.lua @@ -1,15 +1,15 @@ function on_random_update(x, y, z) - local dirtid = block_index('base:dirt'); - if is_solid_at(x, y+1, z) then - set_block(x, y, z, dirtid, 0) + local dirtid = block.index('base:dirt'); + if block.is_solid_at(x, y+1, z) then + block.set(x, y, z, dirtid, 0) else - local grassblockid = block_index('base:grass_block') + local grassblockid = block.index('base:grass_block') for lx=-1,1 do for ly=-1,1 do for lz=-1,1 do - if get_block(x + lx, y + ly, z + lz) == dirtid then - if not is_solid_at(x + lx, y + ly + 1, z + lz) then - set_block(x + lx, y + ly, z + lz, grassblockid, 0) + if block.get(x + lx, y + ly, z + lz) == dirtid then + if not block.is_solid_at(x + lx, y + ly + 1, z + lz) then + block.set(x + lx, y + ly, z + lz, grassblockid, 0) return end end diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index 7ed29c1f..31314ca5 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -35,6 +35,7 @@ function submit(text) add_to_history(text) setup_variables() + document.log.caret = -1 local status, result = pcall(function() return console.execute(text) end) if result ~= nil then local prevtext = document.log.text diff --git a/res/layouts/pages/languages.xml.lua b/res/layouts/pages/languages.xml.lua index a064c68c..2e3710e9 100644 --- a/res/layouts/pages/languages.xml.lua +++ b/res/layouts/pages/languages.xml.lua @@ -9,10 +9,9 @@ function on_open() table.sort(names) local panel = document.root - for _,k in ipairs(names) do - panel:add(string.format( - "", - string.format("core.set_setting('ui.language', %q) menu:back()", invlocales[k]), k + for _,name in ipairs(names) do + panel:add(gui.template( + "language", {id=invlocales[name], name=name} )) end panel:add("") diff --git a/res/layouts/pages/settings_controls.xml.lua b/res/layouts/pages/settings_controls.xml.lua index 879f21e5..c7c8632c 100644 --- a/res/layouts/pages/settings_controls.xml.lua +++ b/res/layouts/pages/settings_controls.xml.lua @@ -16,7 +16,7 @@ function on_open() refresh_sensitivity() local panel = document.bindings_panel - local bindings = core.get_bindings() + local bindings = input.get_bindings() table.sort(bindings, function(a, b) return a > b end) for i,name in ipairs(bindings) do panel:add(gui.template("binding", { diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 835b3746..179d8df8 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -1,6 +1,7 @@ -function create_setting(id, name, step, postfix) +function create_setting(id, name, step, postfix, tooltip) local info = core.get_setting_info(id) postfix = postfix or "" + tooltip = tooltip or "" document.root:add(gui.template("track_setting", { id=id, name=gui.str(name, "settings"), @@ -8,7 +9,8 @@ function create_setting(id, name, step, postfix) min=info.min, max=info.max, step=step, - postfix=postfix + postfix=postfix, + tooltip=tooltip })) update_setting(core.get_setting(id), id, name, postfix) end @@ -24,10 +26,11 @@ function update_setting(x, id, name, postfix) ) end -function create_checkbox(id, name) +function create_checkbox(id, name, tooltip) + tooltip = tooltip or '' document.root:add(string.format( - "%s", - id, core.str_setting(id), gui.str(name, "settings") + "%s", + id, core.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") )) end @@ -35,10 +38,10 @@ function on_open() create_setting("chunks.load-distance", "Load Distance", 1) create_setting("chunks.load-speed", "Load Speed", 1) create_setting("graphics.fog-curve", "Fog Curve", 0.1) - create_setting("graphics.gamma", "Gamma", 0.05) + create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip") create_setting("camera.fov", "FOV", 1, "°") create_checkbox("display.fullscreen", "Fullscreen") create_checkbox("display.vsync", "V-Sync") - create_checkbox("graphics.backlight", "Backlight") + create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") create_checkbox("camera.shaking", "Camera Shaking") end diff --git a/res/layouts/templates/language.xml b/res/layouts/templates/language.xml new file mode 100644 index 00000000..fea34b31 --- /dev/null +++ b/res/layouts/templates/language.xml @@ -0,0 +1,4 @@ + diff --git a/res/layouts/templates/track_setting.xml b/res/layouts/templates/track_setting.xml index 994e5bb3..82ee81c1 100644 --- a/res/layouts/templates/track_setting.xml +++ b/res/layouts/templates/track_setting.xml @@ -1,6 +1,6 @@ diff --git a/res/modules/toml.lua b/res/modules/toml.lua index 33c8faf1..3b66790c 100644 --- a/res/modules/toml.lua +++ b/res/modules/toml.lua @@ -1,2 +1,4 @@ print("WARNING: toml is replaced with built-in library, just remove 'require \"core:toml\"'") +toml.serialize = toml.tostring +toml.deserialize = toml.parse return toml diff --git a/res/texts/be_BY.txt b/res/texts/be_BY.txt index c208eeaf..318a95ff 100644 --- a/res/texts/be_BY.txt +++ b/res/texts/be_BY.txt @@ -12,47 +12,55 @@ error.pack-not-found=Не ўдалося знайсці пакет error.dependency-not-found=Выкарыстоўваная залежнасць не знойдзена pack.remove-confirm=Выдаліць увесь кантэнт які пастаўляецца пакам са свету (беззваротна)? +# Подсказки +graphics.gamma.tooltip=Крывая яркасці асвятлення +graphics.backlight.tooltip=Падсветка, якая прадухіляе поўную цемру + # Меню -menu.New World=Новы Свет -menu.Quit=Выхад -menu.Continue=Працягнуть -menu.Save and Quit to Menu=Захаваць і Выйсці ў Меню -menu.missing-content=Адсутнічае Кантэнт! -menu.Content Error=Памылка Кантэнту -menu.Controls=Кіраванне -menu.Back to Main Menu=Вярнуцца ў Меню -menu.Settings=Налады -menu.Content=Кантэнт +menu.Apply=Ужыць menu.Audio=Гук +menu.Back to Main Menu=Вярнуцца ў Меню +menu.Content Error=Памылка Кантэнту +menu.Content=Кантэнт +menu.Continue=Працягнуть +menu.Controls=Кіраванне +menu.Graphics=Графіка +menu.missing-content=Адсутнічае Кантэнт! +menu.New World=Новы Свет +menu.Page not found=Старонка не знойдзена +menu.Quit=Выхад +menu.Save and Quit to Menu=Захаваць і Выйсці ў Меню +menu.Settings=Налады + world.Seed=Зерне world.Name=Назва - world.World generator=Генератар свету world.generators.default=Звычайны world.generators.flat=Плоскі -menu.Create World=Стварыць Свет - +world.Create World=Стварыць Свет world.convert-request=Ёсць змены ў індэксах! Канвертаваць свет? world.delete-confirm=Выдаліць свет незваротна? # Настройки +settings.Ambient=Фон +settings.Backlight=Падсветка +settings.Camera Shaking=Труска Камеры +settings.Fog Curve=Крывая Туману +settings.FOV=Поле Зроку +settings.Fullscreen=Поўны экран +settings.Gamma=Гама +settings.Language=Мова settings.Load Distance=Дыстанцыя Загрузкі settings.Load Speed=Хуткасць Загрузкі -settings.Fog Curve=Крывая Туману -settings.Backlight=Падсветка -settings.V-Sync=Вертыкальная Сінхранізацыя -settings.Camera Shaking=Труска Камеры settings.Master Volume=Агульная Гучнасць +settings.Mouse Sensitivity=Адчувальнасць Мышы +settings.Music=Музыка settings.Regular Sounds=Звычайныя Гукі settings.UI Sounds=Гукі Інтэрфейсу -settings.Ambient=Фон -settings.Music=Музыка - -settings.FOV=Поле Зроку -settings.Mouse Sensitivity=Адчувальнасць Мышы -settings.Language=Мова +settings.V-Sync=Вертыкальная Сінхранізацыя # Управление +devtools.console=Кансоль movement.forward=Уперад movement.back=Назад movement.left=Улева diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 979c7330..4b8d5e5e 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -8,7 +8,12 @@ world.delete-confirm=Do you want to delete world forever? world.generators.default=Default world.generators.flat=Flat +# Tooltips +graphics.gamma.tooltip=Lighting brightness curve +graphics.backlight.tooltip=Backlight to prevent total darkness + # Bindings +chunks.reload=Reload Chunks devtools.console=Console movement.forward=Forward movement.back=Back diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index ec2946ef..f4cdfbc0 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -12,6 +12,10 @@ error.pack-not-found=Не удалось найти пакет error.dependency-not-found=Используемая зависимость не найдена pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? +# Подсказки +graphics.gamma.tooltip=Кривая яркости освещения +graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту + # Меню menu.Apply=Применить menu.Audio=Звук @@ -56,6 +60,7 @@ settings.UI Sounds=Звуки Интерфейса settings.V-Sync=Вертикальная Синхронизация # Управление +chunks.reload=Перезагрузить Чанки devtools.console=Консоль movement.forward=Вперёд movement.back=Назад diff --git a/src/coders/toml.hpp b/src/coders/toml.hpp index d658eb20..e695a298 100644 --- a/src/coders/toml.hpp +++ b/src/coders/toml.hpp @@ -2,8 +2,6 @@ #define CODERS_TOML_HPP_ #include "../data/dynamic.hpp" -#include "commons.hpp" - #include class SettingsHandler; diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index f3c47b6b..5b7e30b3 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -212,6 +212,10 @@ void ContentLoader::loadBlock(Block& def, std::string name, fs::path file) { root->str("script-name", def.scriptName); root->str("ui-layout", def.uiLayout); root->num("inventory-size", def.inventorySize); + root->num("tick-interval", def.tickInterval); + if (def.tickInterval == 0) { + def.tickInterval = 1; + } if (def.hidden && def.pickingItem == def.name+BLOCK_ITEM_SUFFIX) { def.pickingItem = CORE_EMPTY; diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index e58fbd84..7ff4b972 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -25,11 +25,11 @@ public: enum class DependencyLevel { required, // dependency must be installed optional, // dependency will be installed if found - weak, // dependency will not be installed automatically + weak, // only affects packs order }; -/// @brief Content-pack that should be installed before the dependent +/// @brief Content-pack that should be installed earlier the dependent struct DependencyPack { DependencyLevel level; std::string id; diff --git a/src/core_defs.cpp b/src/core_defs.cpp index 39c70991..57c85bbc 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -27,7 +27,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) { auto bindsFile = paths->getResources()/fs::path("bindings.toml"); if (fs::is_regular_file(bindsFile)) { - Events::loadBindingsToml( + Events::loadBindings( bindsFile.u8string(), files::read_string(bindsFile) ); } diff --git a/src/core_defs.hpp b/src/core_defs.hpp index 884cda26..d57435ad 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -10,6 +10,7 @@ inline const std::string TEXTURE_NOTFOUND = "notfound"; // built-in bindings inline const std::string BIND_DEVTOOLS_CONSOLE = "devtools.console"; +inline const std::string BIND_CHUNKS_RELOAD = "chunks.reload"; inline const std::string BIND_MOVE_FORWARD = "movement.forward"; inline const std::string BIND_MOVE_BACK = "movement.back"; inline const std::string BIND_MOVE_LEFT = "movement.left"; diff --git a/src/data/dynamic.cpp b/src/data/dynamic.cpp index 4d60d2a5..6b87f97c 100644 --- a/src/data/dynamic.cpp +++ b/src/data/dynamic.cpp @@ -214,7 +214,7 @@ void Map::flag(const std::string& key, bool& dst) const { } Map& Map::put(std::string key, const Value& value) { - values.emplace(key, value); + values[key] = value; return *this; } diff --git a/src/debug/Logger.cpp b/src/debug/Logger.cpp index 75582642..b14d441b 100644 --- a/src/debug/Logger.cpp +++ b/src/debug/Logger.cpp @@ -44,13 +44,14 @@ void Logger::log(LogLevel level, const std::string& name, std::string message) { auto ms = duration_cast(system_clock::now().time_since_epoch()) % 1000; ss << " " << std::put_time(std::localtime(&tm), "%Y/%m/%d %T"); ss << '.' << std::setfill('0') << std::setw(3) << ms.count(); - ss << utcOffset << " (" << std::setfill(' ') << std::setw(moduleLen) << name << ") "; + ss << utcOffset << " [" << std::setfill(' ') << std::setw(moduleLen) << name << "] "; ss << message; { std::lock_guard lock(mutex); auto string = ss.str(); if (file.good()) { file << string << '\n'; + file.flush(); } std::cout << string << std::endl; } diff --git a/src/engine.cpp b/src/engine.cpp index fa34c3c7..4720f909 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -58,7 +58,7 @@ inline void create_channel(Engine* engine, std::string name, NumberSetting& sett } engine->keepAlive(setting.observe([=](auto value) { audio::get_channel(name)->setVolume(value*value); - })); + }, true)); } Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths) @@ -71,6 +71,7 @@ Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, Engin if (Window::initialize(&this->settings.display)){ throw initialize_error("could not initialize window"); } + loadControls(); audio::initialize(settings.audio.enabled.get()); create_channel(this, "master", settings.audio.volumeMaster); create_channel(this, "regular", settings.audio.volumeRegular); @@ -94,6 +95,9 @@ Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, Engin addWorldGenerators(); scripting::initialize(this); + + auto resdir = paths->getResources(); + basePacks = files::read_list(resdir/fs::path("config/builtins.list")); } void Engine::loadSettings() { @@ -103,11 +107,22 @@ void Engine::loadSettings() { std::string text = files::read_string(settings_file); toml::parse(settingsHandler, settings_file.string(), text); } +} + +void Engine::loadControls() { fs::path controls_file = paths->getControlsFile(); 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); + } else { + controls_file = paths->getControlsFileOld(); + if (fs::is_regular_file(controls_file)) { + logger.info() << "loading controls (old)"; + std::string text = files::read_string(controls_file); + Events::loadBindingsOld(controls_file.u8string(), text); + fs::remove(controls_file); + } } } @@ -252,6 +267,16 @@ void Engine::loadAssets() { assets.reset(new_assets.release()); } +static void load_configs(const fs::path& root) { + auto configFolder = root/fs::path("config"); + auto bindsFile = configFolder/fs::path("bindings.toml"); + if (fs::is_regular_file(bindsFile)) { + Events::loadBindings( + bindsFile.u8string(), files::read_string(bindsFile) + ); + } +} + void Engine::loadContent() { auto resdir = paths->getResources(); ContentBuilder contentBuilder; @@ -274,14 +299,10 @@ void Engine::loadContent() { ContentLoader loader(&pack); loader.load(contentBuilder); - auto configFolder = pack.folder/fs::path("config"); - auto bindsFile = configFolder/fs::path("bindings.toml"); - if (fs::is_regular_file(bindsFile)) { - Events::loadBindingsToml( - bindsFile.u8string(), files::read_string(bindsFile) - ); - } - } + load_configs(pack.folder); + } + load_configs(paths->getResources()); + content = contentBuilder.build(); resPaths = std::make_unique(resdir, resRoots); diff --git a/src/engine.hpp b/src/engine.hpp index 878b1e54..e9ebeb8a 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -57,7 +57,7 @@ class Engine : public util::ObjectsKeeper { std::recursive_mutex postRunnablesMutex; std::unique_ptr controller; std::unique_ptr interpreter; - std::vector basePacks {"base"}; + std::vector basePacks; uint64_t frame = 0; double lastTime = 0.0; @@ -65,6 +65,7 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr gui; + void loadControls(); void loadSettings(); void saveSettings(); void updateTimers(); diff --git a/src/files/WorldRegions.cpp b/src/files/WorldRegions.cpp index ffc51e8c..166865b1 100644 --- a/src/files/WorldRegions.cpp +++ b/src/files/WorldRegions.cpp @@ -364,7 +364,7 @@ void WorldRegions::put(Chunk* chunk){ chunk->encode(), CHUNK_DATA_LEN, true); // Writing lights cache - if (doWriteLights && chunk->isLighted()) { + if (doWriteLights && chunk->flags.lighted) { put(chunk->x, chunk->z, REGION_LAYER_LIGHTS, chunk->lightmap.encode(), LIGHTMAP_DATA_LEN, true); } diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp index 509c4055..15e80325 100644 --- a/src/files/engine_paths.cpp +++ b/src/files/engine_paths.cpp @@ -10,7 +10,7 @@ #include "WorldFiles.hpp" const fs::path SCREENSHOTS_FOLDER {"screenshots"}; -const fs::path CONTROLS_FILE {"controls.json"}; +const fs::path CONTROLS_FILE {"controls.toml"}; const fs::path SETTINGS_FILE {"settings.toml"}; fs::path EnginePaths::getUserfiles() const { @@ -60,6 +60,10 @@ fs::path EnginePaths::getControlsFile() { return userfiles/fs::path(CONTROLS_FILE); } +fs::path EnginePaths::getControlsFileOld() { + return userfiles/fs::path("controls.json"); +} + fs::path EnginePaths::getSettingsFile() { return userfiles/fs::path(SETTINGS_FILE); } @@ -135,7 +139,7 @@ static fs::path toCanonic(fs::path path) { return path; } -fs::path EnginePaths::resolve(std::string path) { +fs::path EnginePaths::resolve(std::string path, bool throwErr) { size_t separator = path.find(':'); if (separator == std::string::npos) { throw files_access_error("no entry point specified"); @@ -161,7 +165,10 @@ fs::path EnginePaths::resolve(std::string path) { } } } - throw files_access_error("unknown entry point '"+prefix+"'"); + if (throwErr) { + throw files_access_error("unknown entry point '"+prefix+"'"); + } + return fs::path(filename); } ResPaths::ResPaths(fs::path mainRoot, std::vector roots) diff --git a/src/files/engine_paths.hpp b/src/files/engine_paths.hpp index ac1ed5a8..92353e65 100644 --- a/src/files/engine_paths.hpp +++ b/src/files/engine_paths.hpp @@ -29,6 +29,7 @@ public: fs::path getWorldFolder(); fs::path getWorldFolder(const std::string& name); fs::path getControlsFile(); + fs::path getControlsFileOld(); // TODO: remove in 0.22 fs::path getSettingsFile(); bool isWorldNameUsed(std::string name); @@ -39,7 +40,7 @@ public: std::vector scanForWorlds(); - fs::path resolve(std::string path); + fs::path resolve(std::string path, bool throwErr=true); }; struct PathsRoot { diff --git a/src/files/files.hpp b/src/files/files.hpp index 560d5e1f..142deb25 100644 --- a/src/files/files.hpp +++ b/src/files/files.hpp @@ -20,7 +20,7 @@ namespace files { std::ifstream file; size_t filelength; public: - rafile(std::filesystem::path filename); + rafile(fs::path filename); void seekg(std::streampos pos); void read(char* buffer, std::streamsize size); diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 014af2f0..4362b189 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using namespace gui; @@ -72,15 +73,24 @@ std::shared_ptr create_debug_panel( L" visible: "+std::to_wstring(level->chunks->visible); })); panel->add(create_label([=](){ - auto* indices = level->content->getIndices(); - auto def = indices->getBlockDef(player->selectedVoxel.id); std::wstringstream stream; - stream << std::hex << player->selectedVoxel.states; - if (def) { - stream << L" (" << util::str2wstr_utf8(def->name) << L")"; + stream << "r:" << player->selectedVoxel.state.rotation << " s:" + << player->selectedVoxel.state.segment << " u:" + << std::bitset<8>(player->selectedVoxel.state.userbits); + if (player->selectedVoxel.id == BLOCK_VOID) { + return std::wstring {L"block: -"}; + } else { + return L"block: "+std::to_wstring(player->selectedVoxel.id)+ + L" "+stream.str(); + } + })); + panel->add(create_label([=](){ + auto* indices = level->content->getIndices(); + if (auto def = indices->getBlockDef(player->selectedVoxel.id)) { + return L"name: " + util::str2wstr_utf8(def->name); + } else { + return std::wstring {L"name: void"}; } - return L"block: "+std::to_wstring(player->selectedVoxel.id)+ - L" "+stream.str(); })); panel->add(create_label([=](){ return L"seed: "+std::to_wstring(level->getWorld()->getSeed()); @@ -139,8 +149,8 @@ std::shared_ptr create_debug_panel( } { auto bar = std::make_shared(0.0f, 1.0f, 0.0f, 0.005f, 8); - bar->setSupplier([=]() {return WorldRenderer::fog;}); - bar->setConsumer([=](double val) {WorldRenderer::fog = val;}); + bar->setSupplier([=]() {return level->getWorld()->fog;}); + bar->setConsumer([=](double val) {level->getWorld()->fog = val;}); panel->add(bar); } { diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 95aa69f7..42be78f9 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -333,7 +333,7 @@ void Hud::openInventory( if (blockinv == nullptr) { blockinv = level->inventories->createVirtual(blockUI->getSlotsCount()); } - level->chunks->getChunkByVoxel(block.x, block.y, block.z)->setUnsaved(true); + level->chunks->getChunkByVoxel(block.x, block.y, block.z)->flags.unsaved = true; blockUI->bind(blockinv, content); blockPos = block; currentblockid = level->chunks->get(block.x, block.y, block.z)->id; diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 5814f3bf..d740ce19 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -1,5 +1,6 @@ #include "LevelScreen.hpp" +#include "../../core_defs.hpp" #include "../hud.hpp" #include "../LevelFrontend.hpp" #include "../../audio/audio.hpp" @@ -47,6 +48,9 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr level) keepAlive(settings.camera.fov.observe([=](double value) { controller->getPlayer()->camera->setFov(glm::radians(value)); })); + keepAlive(Events::getBinding(BIND_CHUNKS_RELOAD).onactived.add([=](){ + controller->getLevel()->chunks->saveAndClear(); + })); animator = std::make_unique(); animator->addAnimations(assets->getAnimations()); @@ -114,9 +118,6 @@ void LevelScreen::updateHotkeys() { if (Events::jpressed(keycode::F3)) { controller->getPlayer()->debug = !controller->getPlayer()->debug; } - if (Events::jpressed(keycode::F5)) { - controller->getLevel()->chunks->saveAndClear(); - } } void LevelScreen::update(float delta) { diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp index c07c6632..6e80af51 100644 --- a/src/graphics/core/Batch2D.cpp +++ b/src/graphics/core/Batch2D.cpp @@ -13,19 +13,18 @@ Batch2D::Batch2D(size_t capacity) : capacity(capacity), color(1.0f){ {2}, {2}, {4}, {0} }; - buffer = new float[capacity * B2D_VERTEX_SIZE]; - mesh = std::make_unique(buffer, 0, attrs); + buffer = std::make_unique(capacity * B2D_VERTEX_SIZE); + mesh = std::make_unique(buffer.get(), 0, attrs); index = 0; ubyte pixels[] = { 0xFF, 0xFF, 0xFF, 0xFF }; blank = std::make_unique(pixels, 1, 1, ImageFormat::rgba8888); - _texture = nullptr; + currentTexture = nullptr; } Batch2D::~Batch2D(){ - delete[] buffer; } void Batch2D::setPrimitive(DrawPrimitive primitive) { @@ -37,7 +36,7 @@ void Batch2D::setPrimitive(DrawPrimitive primitive) { } void Batch2D::begin(){ - _texture = nullptr; + currentTexture = nullptr; blank->bind(); color = glm::vec4(1.0f); primitive = DrawPrimitive::triangle; @@ -73,14 +72,16 @@ void Batch2D::vertex( } void Batch2D::texture(Texture* new_texture){ - if (_texture == new_texture) + if (currentTexture == new_texture) { return; + } flush(); - _texture = new_texture; - if (new_texture == nullptr) + currentTexture = new_texture; + if (new_texture == nullptr) { blank->bind(); - else + } else { new_texture->bind(); + } } void Batch2D::untexture() { @@ -327,7 +328,7 @@ void Batch2D::sprite(float x, float y, float w, float h, int atlasRes, int index void Batch2D::flush() { if (index == 0) return; - mesh->reload(buffer, index / B2D_VERTEX_SIZE); + mesh->reload(buffer.get(), index / B2D_VERTEX_SIZE); mesh->draw(gl::to_glenum(primitive)); index = 0; } diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp index cbd56908..18095cff 100644 --- a/src/graphics/core/Batch2D.hpp +++ b/src/graphics/core/Batch2D.hpp @@ -12,13 +12,13 @@ class Texture; struct UVRegion; class Batch2D { - float* buffer; + std::unique_ptr buffer; size_t capacity; std::unique_ptr mesh; std::unique_ptr blank; size_t index; glm::vec4 color; - Texture* _texture; + Texture* currentTexture; DrawPrimitive primitive = DrawPrimitive::triangle; void setPrimitive(DrawPrimitive primitive); diff --git a/src/graphics/core/Batch3D.cpp b/src/graphics/core/Batch3D.cpp index 105c8623..7481ec6f 100644 --- a/src/graphics/core/Batch3D.cpp +++ b/src/graphics/core/Batch3D.cpp @@ -14,28 +14,29 @@ Batch3D::Batch3D(size_t capacity) {3}, {2}, {4}, {0} }; - buffer = new float[capacity * B3D_VERTEX_SIZE]; - mesh = std::make_unique(buffer, 0, attrs); + buffer = std::make_unique(capacity * B3D_VERTEX_SIZE); + mesh = std::make_unique(buffer.get(), 0, attrs); index = 0; ubyte pixels[] = { 255, 255, 255, 255, }; blank = std::make_unique(pixels, 1, 1, ImageFormat::rgba8888); - _texture = nullptr; + currentTexture = nullptr; } Batch3D::~Batch3D(){ - delete[] buffer; } void Batch3D::begin(){ - _texture = nullptr; + currentTexture = nullptr; blank->bind(); } -void Batch3D::vertex(float x, float y, float z, float u, float v, - float r, float g, float b, float a) { +void Batch3D::vertex( + float x, float y, float z, float u, float v, + float r, float g, float b, float a +) { buffer[index++] = x; buffer[index++] = y; buffer[index++] = z; @@ -46,8 +47,10 @@ void Batch3D::vertex(float x, float y, float z, float u, float v, buffer[index++] = b; buffer[index++] = a; } -void Batch3D::vertex(glm::vec3 coord, float u, float v, - float r, float g, float b, float a) { +void Batch3D::vertex( + glm::vec3 coord, float u, float v, + float r, float g, float b, float a +) { buffer[index++] = coord.x; buffer[index++] = coord.y; buffer[index++] = coord.z; @@ -58,9 +61,11 @@ void Batch3D::vertex(glm::vec3 coord, float u, float v, buffer[index++] = b; buffer[index++] = a; } -void Batch3D::vertex(glm::vec3 point, - glm::vec2 uvpoint, - float r, float g, float b, float a) { +void Batch3D::vertex( + glm::vec3 point, + glm::vec2 uvpoint, + float r, float g, float b, float a +) { buffer[index++] = point.x; buffer[index++] = point.y; buffer[index++] = point.z; @@ -99,10 +104,10 @@ void Batch3D::face( } void Batch3D::texture(Texture* new_texture){ - if (_texture == new_texture) + if (currentTexture == new_texture) return; flush(); - _texture = new_texture; + currentTexture = new_texture; if (new_texture == nullptr) blank->bind(); else @@ -166,7 +171,9 @@ inline glm::vec4 do_tint(float value) { return glm::vec4(value, value, value, 1.0f); } -void Batch3D::xSprite(float w, float h, const UVRegion& uv, const glm::vec4 tint, bool shading) { +void Batch3D::xSprite( + float w, float h, const UVRegion& uv, const glm::vec4 tint, bool shading +) { face( glm::vec3(-w * 0.25f, 0.0f, -w * 0.25f), w, h, @@ -244,13 +251,13 @@ void Batch3D::point(glm::vec3 coord, glm::vec4 tint) { } void Batch3D::flush() { - mesh->reload(buffer, index / B3D_VERTEX_SIZE); + mesh->reload(buffer.get(), index / B3D_VERTEX_SIZE); mesh->draw(); index = 0; } void Batch3D::flushPoints() { - mesh->reload(buffer, index / B3D_VERTEX_SIZE); + mesh->reload(buffer.get(), index / B3D_VERTEX_SIZE); mesh->draw(GL_POINTS); index = 0; } diff --git a/src/graphics/core/Batch3D.hpp b/src/graphics/core/Batch3D.hpp index d6cda86b..6ff42caa 100644 --- a/src/graphics/core/Batch3D.hpp +++ b/src/graphics/core/Batch3D.hpp @@ -12,28 +12,35 @@ class Mesh; class Texture; class Batch3D { - float* buffer; + std::unique_ptr buffer; size_t capacity; std::unique_ptr mesh; std::unique_ptr blank; size_t index; - Texture* _texture; + Texture* currentTexture; - void vertex(float x, float y, float z, - float u, float v, - float r, float g, float b, float a); - void vertex(glm::vec3 coord, - float u, float v, - float r, float g, float b, float a); - void vertex(glm::vec3 point, glm::vec2 uvpoint, - float r, float g, float b, float a); - - void face(const glm::vec3& coord, float w, float h, + void vertex( + float x, float y, float z, + float u, float v, + float r, float g, float b, float a + ); + void vertex( + glm::vec3 coord, + float u, float v, + float r, float g, float b, float a + ); + void vertex( + glm::vec3 point, glm::vec2 uvpoint, + float r, float g, float b, float a + ); + void face( + const glm::vec3& coord, float w, float h, const glm::vec3& axisX, const glm::vec3& axisY, const UVRegion& region, - const glm::vec4& tint); + const glm::vec4& tint + ); public: Batch3D(size_t capacity); diff --git a/src/graphics/core/Texture.hpp b/src/graphics/core/Texture.hpp index b5d28617..c95bab27 100644 --- a/src/graphics/core/Texture.hpp +++ b/src/graphics/core/Texture.hpp @@ -4,7 +4,6 @@ #include "../../typedefs.hpp" #include "ImageData.hpp" -#include #include class Texture { diff --git a/src/graphics/render/BlocksPreview.cpp b/src/graphics/render/BlocksPreview.cpp index 96a8a718..eb7e72cf 100644 --- a/src/graphics/render/BlocksPreview.cpp +++ b/src/graphics/render/BlocksPreview.cpp @@ -44,13 +44,18 @@ std::unique_ptr BlocksPreview::draw( break; case BlockModel::aabb: { - glm::vec3 hitbox = glm::vec3(); - for (const auto& box : def->hitboxes) + glm::vec3 hitbox {}; + for (const auto& box : def->hitboxes) { hitbox = glm::max(hitbox, box.size()); - offset.y += (1.0f - hitbox).y * 0.5f; + } + offset = glm::vec3(1, 1, 0.0f); shader->uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); - batch->blockCube(hitbox * glm::vec3(size * 0.63f), - texfaces, glm::vec4(1.0f), !def->rt.emissive); + batch->cube( + -hitbox * glm::vec3(size * 0.63f)*0.5f * glm::vec3(1,1,-1), + hitbox * glm::vec3(size * 0.63f), + texfaces, glm::vec4(1.0f), + !def->rt.emissive + ); } batch->flush(); break; @@ -138,7 +143,7 @@ std::unique_ptr BlocksPreview::build( shader->uniformMatrix("u_projview", glm::ortho(0.0f, float(iconSize), 0.0f, float(iconSize), -100.0f, 100.0f) * - glm::lookAt(glm::vec3(2, 2, 2), + glm::lookAt(glm::vec3(0.57735f), glm::vec3(0.0f), glm::vec3(0, 1, 0))); diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 4b1779d4..9420d070 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -219,10 +219,13 @@ void BlocksRenderer::blockXSprite(int x, int y, int z, // HINT: texture faces order: {east, west, bottom, top, south, north} /* AABB blocks render method */ -void BlocksRenderer::blockAABB(const ivec3& icoord, - const UVRegion(&texfaces)[6], - const Block* block, ubyte rotation, - bool lights) { +void BlocksRenderer::blockAABB( + const ivec3& icoord, + const UVRegion(&texfaces)[6], + const Block* block, + ubyte rotation, + bool lights +) { if (block->hitboxes.empty()) { return; } @@ -301,11 +304,13 @@ void BlocksRenderer::blockCustomModel(const ivec3& icoord, } /* Fastest solid shaded blocks render method */ -void BlocksRenderer::blockCube(int x, int y, int z, - const UVRegion(&texfaces)[6], - const Block* block, - ubyte states, - bool lights) { +void BlocksRenderer::blockCube( + int x, int y, int z, + const UVRegion(&texfaces)[6], + const Block* block, + blockstate states, + bool lights +) { ubyte group = block->drawGroup; vec3 X(1, 0, 0); @@ -314,7 +319,7 @@ void BlocksRenderer::blockCube(int x, int y, int z, vec3 coord(x, y, z); if (block->rotatable) { auto& rotations = block->rotations; - auto& orient = rotations.variants[states & BLOCK_ROT_MASK]; + auto& orient = rotations.variants[states.rotation]; X = orient.axisX; Y = orient.axisY; Z = orient.axisZ; @@ -423,7 +428,7 @@ void BlocksRenderer::render(const voxel* voxels) { int z = (i / CHUNK_D) % CHUNK_W; switch (def.model) { case BlockModel::block: - blockCube(x, y, z, texfaces, &def, vox.states, !def.rt.emissive); + blockCube(x, y, z, texfaces, &def, vox.state, !def.rt.emissive); break; case BlockModel::xsprite: { blockXSprite(x, y, z, vec3(1.0f), @@ -431,11 +436,11 @@ void BlocksRenderer::render(const voxel* voxels) { break; } case BlockModel::aabb: { - blockAABB(ivec3(x,y,z), texfaces, &def, vox.rotation(), !def.rt.emissive); + blockAABB(ivec3(x,y,z), texfaces, &def, vox.state.rotation, !def.rt.emissive); break; } case BlockModel::custom: { - blockCustomModel(ivec3(x, y, z), &def, vox.rotation(), !def.rt.emissive); + blockCustomModel(ivec3(x, y, z), &def, vox.state.rotation, !def.rt.emissive); break; } default: diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index 23b6d625..244d4674 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -70,16 +70,33 @@ class BlocksRenderer { const UVRegion& texreg, bool lights); - void blockCube(int x, int y, int z, const UVRegion(&faces)[6], const Block* block, ubyte states, bool lights); - void blockAABB(const glm::ivec3& coord, - const UVRegion(&faces)[6], - const Block* block, - ubyte rotation, - bool lights); - void blockXSprite(int x, int y, int z, const glm::vec3& size, const UVRegion& face1, const UVRegion& face2, float spread); - void blockCustomModel(const glm::ivec3& icoord, - const Block* block, ubyte rotation, - bool lights); + void blockCube( + int x, int y, int z, + const UVRegion(&faces)[6], + const Block* block, + blockstate states, + bool lights + ); + void blockAABB( + const glm::ivec3& coord, + const UVRegion(&faces)[6], + const Block* block, + ubyte rotation, + bool lights + ); + void blockXSprite( + int x, int y, int z, + const glm::vec3& size, + const UVRegion& face1, + const UVRegion& face2, + float spread + ); + void blockCustomModel( + const glm::ivec3& icoord, + const Block* block, + ubyte rotation, + bool lights + ); bool isOpenForLight(int x, int y, int z) const; bool isOpen(int x, int y, int z, ubyte group) const; diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index 7f3f3959..d1011a10 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -56,19 +56,16 @@ ChunksRenderer::~ChunksRenderer() { } std::shared_ptr ChunksRenderer::render(std::shared_ptr chunk, bool important) { - chunk->setModified(false); - + chunk->flags.modified = false; if (important) { auto mesh = renderer->render(chunk.get(), level->chunksStorage.get()); meshes[glm::ivec2(chunk->x, chunk->z)] = mesh; return mesh; } - glm::ivec2 key(chunk->x, chunk->z); if (inwork.find(key) != inwork.end()) { return nullptr; } - inwork[key] = true; threadPool.enqueueJob(chunk); return nullptr; @@ -86,7 +83,7 @@ std::shared_ptr ChunksRenderer::getOrRender(std::shared_ptr chunk, if (found == meshes.end()) { return render(chunk, important); } - if (chunk->isModified()) { + if (chunk->flags.modified) { render(chunk, important); } return found->second; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 6e5888ce..8d02a750 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -37,6 +37,9 @@ #include #include +#include +#include + bool WorldRenderer::showChunkBorders = false; WorldRenderer::WorldRenderer(Engine* engine, LevelFrontend* frontend, Player* player) @@ -76,7 +79,7 @@ bool WorldRenderer::drawChunk( bool culling ){ auto chunk = level->chunks->chunks[index]; - if (!chunk->isLighted()) { + if (!chunk->flags.lighted) { return false; } float distance = glm::distance( @@ -160,6 +163,7 @@ void WorldRenderer::renderLevel( shader->use(); shader->uniformMatrix("u_proj", camera->getProjection()); shader->uniformMatrix("u_view", camera->getView()); + shader->uniform1f("u_timer", Window::time()); shader->uniform1f("u_gamma", settings.graphics.gamma.get()); shader->uniform1f("u_fogFactor", fogFactor); shader->uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); @@ -194,19 +198,19 @@ void WorldRenderer::renderBlockSelection(Camera* camera, Shader* linesShader) { auto indices = level->content->getIndices(); blockid_t id = PlayerController::selectedBlockId; auto block = indices->getBlockDef(id); - const glm::vec3 pos = PlayerController::selectedBlockPosition; + const glm::ivec3 pos = player->selectedBlockPosition; const glm::vec3 point = PlayerController::selectedPointPosition; const glm::vec3 norm = PlayerController::selectedBlockNormal; const std::vector& hitboxes = block->rotatable - ? block->rt.hitboxes[PlayerController::selectedBlockStates] + ? block->rt.hitboxes[PlayerController::selectedBlockRotation] : block->hitboxes; linesShader->use(); linesShader->uniformMatrix("u_projview", camera->getProjView()); lineBatch->lineWidth(2.0f); for (auto& hitbox: hitboxes) { - const glm::vec3 center = pos + hitbox.center(); + const glm::vec3 center = glm::vec3(pos) + hitbox.center(); const glm::vec3 size = hitbox.size(); lineBatch->box(center, size + glm::vec3(0.02), glm::vec4(0.f, 0.f, 0.f, 0.5f)); if (player->debug) { @@ -274,11 +278,12 @@ void WorldRenderer::draw( bool hudVisible, PostProcessing* postProcessing ){ + auto world = level->getWorld(); const Viewport& vp = pctx.getViewport(); camera->aspect = vp.getWidth() / static_cast(vp.getHeight()); const EngineSettings& settings = engine->getSettings(); - skybox->refresh(pctx, level->getWorld()->daytime, 1.0f+fog*2.0f, 4); + skybox->refresh(pctx, world->daytime, 1.0f+world->fog*2.0f, 4); Assets* assets = engine->getAssets(); Shader* linesShader = assets->getShader("lines"); @@ -291,7 +296,7 @@ void WorldRenderer::draw( Window::clearDepth(); // Drawing background sky plane - skybox->draw(pctx, camera, assets, level->getWorld()->daytime, fog); + skybox->draw(pctx, camera, assets, world->daytime, world->fog); // Actually world render with depth buffer on { @@ -355,5 +360,3 @@ void WorldRenderer::drawBorders(int sx, int sy, int sz, int ex, int ey, int ez) } lineBatch->render(); } - -float WorldRenderer::fog = 0.0f; diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index b99be9ae..27d1a569 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -8,8 +8,6 @@ #include #include -#include -#include class Level; class Player; @@ -76,8 +74,6 @@ public: Camera* camera, const EngineSettings& settings ); - - static float fog; }; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 63576877..c7ef8a1f 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -68,27 +68,25 @@ void GUI::updateTooltip(float delta) { if (hover == nullptr || !hover->isInside(Events::cursor)) { return resetTooltip(); } - float mouseDelta = glm::length(Events::delta); - if (mouseDelta < 1.0f || tooltipTimer >= hover->getTooltipDelay()) { - if (tooltipTimer + delta >= hover->getTooltipDelay()) { - auto label = std::dynamic_pointer_cast(get("tooltip.label")); - const auto& text = hover->getTooltip(); - if (label && !text.empty()) { - tooltip->setVisible(true); - label->setText(langs::get(text)); - auto size = label->getSize()+glm::vec2(4.0f); - auto pos = Events::cursor+glm::vec2(10.0f); - auto rootSize = container->getSize(); - pos.x = glm::min(pos.x, rootSize.x-size.x); - pos.y = glm::min(pos.y, rootSize.y-size.y); - tooltip->setSize(size); - tooltip->setPos(pos); - } + if (tooltipTimer + delta >= hover->getTooltipDelay()) { + auto label = std::dynamic_pointer_cast(get("tooltip.label")); + const auto& text = hover->getTooltip(); + if (text.empty() && tooltip->isVisible()) { + return resetTooltip(); + } + if (label && !text.empty()) { + tooltip->setVisible(true); + label->setText(langs::get(text)); + auto size = label->getSize()+glm::vec2(4.0f); + auto pos = Events::cursor+glm::vec2(10.0f); + auto rootSize = container->getSize(); + pos.x = glm::min(pos.x, rootSize.x-size.x); + pos.y = glm::min(pos.y, rootSize.y-size.y); + tooltip->setSize(size); + tooltip->setPos(pos); } - tooltipTimer += delta; - } else { - resetTooltip(); } + tooltipTimer += delta; } /// @brief Mouse related input and logic handling diff --git a/src/graphics/ui/elements/CheckBox.hpp b/src/graphics/ui/elements/CheckBox.hpp index e366768e..cc0593c8 100644 --- a/src/graphics/ui/elements/CheckBox.hpp +++ b/src/graphics/ui/elements/CheckBox.hpp @@ -50,6 +50,11 @@ namespace gui { virtual bool isChecked() const { return checkbox->isChecked(); } + + virtual void setTooltip(const std::wstring& text) override { + Panel::setTooltip(text); + checkbox->setTooltip(text); + } }; } diff --git a/src/graphics/ui/elements/Container.cpp b/src/graphics/ui/elements/Container.cpp index fe8d77ba..e31cde11 100644 --- a/src/graphics/ui/elements/Container.cpp +++ b/src/graphics/ui/elements/Container.cpp @@ -127,6 +127,14 @@ void Container::remove(std::shared_ptr selected) { refresh(); } +void Container::remove(const std::string& id) { + for (auto& node : nodes) { + if (node->getId() == id) { + return remove(node); + } + } +} + void Container::clear() { for (auto node : nodes) { node->setParent(nullptr); diff --git a/src/graphics/ui/elements/Container.hpp b/src/graphics/ui/elements/Container.hpp index 8aa38b2f..be385f35 100644 --- a/src/graphics/ui/elements/Container.hpp +++ b/src/graphics/ui/elements/Container.hpp @@ -26,6 +26,7 @@ namespace gui { virtual void add(std::shared_ptr node, glm::vec2 pos); virtual void clear(); virtual void remove(std::shared_ptr node); + virtual void remove(const std::string& id); virtual void scrolled(int value) override; virtual void setScrollable(bool flag); void listenInterval(float interval, ontimeout callback, int repeat=-1); diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 43b6304e..ac454c42 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -337,6 +337,9 @@ inline std::wstring get_alphabet(wchar_t c) { } void TextBox::tokenSelectAt(int index) { + if (input.empty()) { + return; + } int left = index; int right = index; diff --git a/src/lighting/LightSolver.cpp b/src/lighting/LightSolver.cpp index 0656ee53..f9865956 100644 --- a/src/lighting/LightSolver.cpp +++ b/src/lighting/LightSolver.cpp @@ -21,7 +21,7 @@ void LightSolver::add(int x, int y, int z, int emission) { addqueue.push(lightentry {x, y, z, ubyte(emission)}); Chunk* chunk = chunks->getChunkByVoxel(x, y, z); - chunk->setModified(true); + chunk->flags.modified = true; chunk->lightmap.set(x-chunk->x*CHUNK_W, y, z-chunk->z*CHUNK_D, channel, emission); } @@ -67,7 +67,7 @@ void LightSolver::solve(){ if (chunk) { int lx = x - chunk->x * CHUNK_W; int lz = z - chunk->z * CHUNK_D; - chunk->setModified(true); + chunk->flags.modified = true; ubyte light = chunk->lightmap.get(lx,y,lz, channel); if (light != 0 && light == entry.light-1){ @@ -96,7 +96,7 @@ void LightSolver::solve(){ if (chunk) { int lx = x - chunk->x * CHUNK_W; int lz = z - chunk->z * CHUNK_D; - chunk->setModified(true); + chunk->flags.modified = true; ubyte light = chunk->lightmap.get(lx, y, lz, channel); voxel& v = chunk->voxels[vox_index(lx, y, lz)]; diff --git a/src/logic/BlocksController.cpp b/src/logic/BlocksController.cpp index 6cbb4aba..9ddf12b8 100644 --- a/src/logic/BlocksController.cpp +++ b/src/logic/BlocksController.cpp @@ -53,8 +53,8 @@ int Clock::getTickId() const { BlocksController::BlocksController(Level* level, uint padding) : level(level), - chunks(level->chunks.get()), - lighting(level->lighting.get()), + chunks(level->chunks.get()), + lighting(level->lighting.get()), randTickClock(20, 3), blocksTickClock(20, 1), worldTickClock(20, 1), @@ -71,7 +71,7 @@ void BlocksController::updateSides(int x, int y, int z) { } void BlocksController::breakBlock(Player* player, const Block* def, int x, int y, int z) { - chunks->set(x,y,z, 0, 0); + chunks->set(x,y,z, 0, {}); lighting->onBlockSet(x,y,z, 0); if (def->rt.funcsset.onbroken) { scripting::on_block_broken(player, def, x, y, z); @@ -113,8 +113,9 @@ void BlocksController::onBlocksTick(int tickid, int parts) { if ((id + tickid) % parts != 0) continue; auto def = indices->getBlockDef(id); - if (def->rt.funcsset.onblockstick) { - scripting::on_blocks_tick(def, tickRate); + auto interval = def->tickInterval; + if (def->rt.funcsset.onblockstick && tickid / parts % interval == 0) { + scripting::on_blocks_tick(def, tickRate / interval); } } } @@ -132,7 +133,7 @@ void BlocksController::randomTick(int tickid, int parts) { if ((index + tickid) % parts != 0) continue; auto& chunk = chunks->chunks[index]; - if (chunk == nullptr || !chunk->isLighted()) + if (chunk == nullptr || !chunk->flags.lighted) continue; for (int s = 0; s < segments; s++) { for (int i = 0; i < 4; i++) { @@ -150,49 +151,49 @@ void BlocksController::randomTick(int tickid, int parts) { } } } - } + } } int64_t BlocksController::createBlockInventory(int x, int y, int z) { - auto chunk = chunks->getChunkByVoxel(x, y, z); - if (chunk == nullptr) { - return 0; - } - int lx = x - chunk->x * CHUNK_W; - int lz = z - chunk->z * CHUNK_D; - auto inv = chunk->getBlockInventory(lx, y, lz); - if (inv == nullptr) { + auto chunk = chunks->getChunkByVoxel(x, y, z); + if (chunk == nullptr) { + return 0; + } + int lx = x - chunk->x * CHUNK_W; + int lz = z - chunk->z * CHUNK_D; + auto inv = chunk->getBlockInventory(lx, y, lz); + if (inv == nullptr) { auto indices = level->content->getIndices(); auto def = indices->getBlockDef(chunk->voxels[vox_index(lx, y, lz)].id); int invsize = def->inventorySize; if (invsize == 0) { return 0; } - inv = level->inventories->create(invsize); + inv = level->inventories->create(invsize); chunk->addBlockInventory(inv, lx, y, lz); - } + } return inv->getId(); } void BlocksController::bindInventory(int64_t invid, int x, int y, int z) { auto chunk = chunks->getChunkByVoxel(x, y, z); - if (chunk == nullptr) { - throw std::runtime_error("block does not exists"); - } + if (chunk == nullptr) { + throw std::runtime_error("block does not exists"); + } if (invid <= 0) { throw std::runtime_error("unable to bind virtual inventory"); } - int lx = x - chunk->x * CHUNK_W; - int lz = z - chunk->z * CHUNK_D; + int lx = x - chunk->x * CHUNK_W; + int lz = z - chunk->z * CHUNK_D; chunk->addBlockInventory(level->inventories->get(invid), lx, y, lz); } void BlocksController::unbindInventory(int x, int y, int z) { auto chunk = chunks->getChunkByVoxel(x, y, z); - if (chunk == nullptr) { - throw std::runtime_error("block does not exists"); - } + if (chunk == nullptr) { + throw std::runtime_error("block does not exists"); + } int lx = x - chunk->x * CHUNK_W; - int lz = z - chunk->z * CHUNK_D; + int lz = z - chunk->z * CHUNK_D; chunk->removeBlockInventory(lx, y, lz); } diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index 1c039a14..56eab88c 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -61,7 +61,7 @@ bool ChunksController::loadVisible(){ int index = z * w + x; auto& chunk = chunks->chunks[index]; if (chunk != nullptr){ - if (chunk->isLoaded() && !chunk->isLighted()) { + if (chunk->flags.loaded && !chunk->flags.lighted) { if (buildLights(chunk)) { return true; } @@ -99,12 +99,12 @@ bool ChunksController::buildLights(std::shared_ptr chunk) { } } if (surrounding == MIN_SURROUNDING) { - bool lightsCache = chunk->isLoadedLights(); + bool lightsCache = chunk->flags.loadedLights; if (!lightsCache) { lighting->buildSkyLight(chunk->x, chunk->z); } lighting->onChunkLoaded(chunk->x, chunk->z, !lightsCache); - chunk->setLighted(true); + chunk->flags.lighted = true; return true; } return false; @@ -114,20 +114,20 @@ void ChunksController::createChunk(int x, int z) { auto chunk = level->chunksStorage->create(x, z); chunks->putChunk(chunk); - if (!chunk->isLoaded()) { + if (!chunk->flags.loaded) { generator->generate( chunk->voxels, x, z, level->getWorld()->getSeed() ); - chunk->setUnsaved(true); + chunk->flags.unsaved = true; } chunk->updateHeights(); - if (!chunk->isLoadedLights()) { + if (!chunk->flags.loadedLights) { Lighting::prebuildSkyLight( chunk.get(), level->content->getIndices() ); } - chunk->setLoaded(true); - chunk->setReady(true); + chunk->flags.loaded = true; + chunk->flags.ready = true; } diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 44e73512..beb452f6 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -166,11 +166,10 @@ void CameraControl::update(const PlayerInput& input, float delta, Chunks* chunks } } -glm::vec3 PlayerController::selectedBlockPosition; glm::vec3 PlayerController::selectedPointPosition; glm::ivec3 PlayerController::selectedBlockNormal; int PlayerController::selectedBlockId = -1; -int PlayerController::selectedBlockStates = 0; +int PlayerController::selectedBlockRotation = 0; PlayerController::PlayerController( Level* level, @@ -250,7 +249,7 @@ void PlayerController::update(float delta, bool input, bool pause) { updateInteraction(); } else { selectedBlockId = -1; - selectedBlockStates = 0; + selectedBlockRotation = 0; } } @@ -362,11 +361,11 @@ void PlayerController::updateInteraction(){ maxDistance, end, norm, iend ); - if (vox != nullptr){ + if (vox != nullptr) { player->selectedVoxel = *vox; selectedBlockId = vox->id; - selectedBlockStates = vox->states; - selectedBlockPosition = iend; + selectedBlockRotation = vox->state.rotation; + player->selectedBlockPosition = iend; selectedPointPosition = end; selectedBlockNormal = norm; int x = iend.x; @@ -374,7 +373,8 @@ void PlayerController::updateInteraction(){ int z = iend.z; Block* def = indices->getBlockDef(item->rt.placingBlock); - uint8_t states = determine_rotation(def, norm, camera->dir); + blockstate state {}; + state.rotation = determine_rotation(def, norm, camera->dir); if (lclick && !input.shift && item->rt.funcsset.on_block_break_by) { if (scripting::on_item_break_block(player.get(), item, x, y, z)) @@ -411,13 +411,13 @@ void PlayerController::updateInteraction(){ z = (iend.z)+(norm.z); } else { if (def->rotations.name == "pipe") { - states = BLOCK_DIR_UP; + state.rotation = BLOCK_DIR_UP; } } vox = chunks->get(x, y, z); blockid_t chosenBlock = def->rt.id; if (vox && (target = indices->getBlockDef(vox->id))->replaceable) { - if (!level->physics->isBlockInside(x,y,z,def,states, player->hitbox.get()) + if (!level->physics->isBlockInside(x,y,z,def,state, player->hitbox.get()) || !def->obstacle){ if (def->grounded && !chunks->isSolidBlock(x, y-1, z)) { chosenBlock = 0; @@ -427,7 +427,7 @@ void PlayerController::updateInteraction(){ glm::ivec3(x, y, z), def, BlockInteraction::placing ); - chunks->set(x, y, z, chosenBlock, states); + chunks->set(x, y, z, chosenBlock, state); lighting->onBlockSet(x,y,z, chosenBlock); if (def->rt.funcsset.onplaced) { scripting::on_block_placed(player.get(), def, x, y, z); @@ -442,12 +442,13 @@ void PlayerController::updateInteraction(){ } } else { selectedBlockId = -1; - selectedBlockStates = 0; - } - if (rclick) { - if (item->rt.funcsset.on_use) { - scripting::on_item_use(player.get(), item); - } + selectedBlockRotation = 0; + player->selectedVoxel.id = BLOCK_VOID; + if (rclick) { + if (item->rt.funcsset.on_use) { + scripting::on_item_use(player.get(), item); + } + } } } diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index d48a3a51..46e5cbca 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -77,11 +77,10 @@ class PlayerController { void onFootstep(); void updateFootsteps(float delta); public: - static glm::vec3 selectedBlockPosition; static glm::ivec3 selectedBlockNormal; static glm::vec3 selectedPointPosition; static int selectedBlockId; - static int selectedBlockStates; + static int selectedBlockRotation; PlayerController( Level* level, diff --git a/src/logic/scripting/lua/libblock.cpp b/src/logic/scripting/lua/libblock.cpp index 695d8e36..22e76b84 100644 --- a/src/logic/scripting/lua/libblock.cpp +++ b/src/logic/scripting/lua/libblock.cpp @@ -16,7 +16,7 @@ int l_block_name(lua_State* L) { auto indices = scripting::content->getIndices(); lua_Integer id = lua_tointeger(L, 1); - if (id < 0 || size_t(id) >= indices->countBlockDefs()) { + if (static_cast(id) >= indices->countBlockDefs()) { return 0; } auto def = indices->getBlockDef(id); @@ -27,7 +27,7 @@ int l_block_name(lua_State* L) { int l_block_material(lua_State* L) { auto indices = scripting::content->getIndices(); lua_Integer id = lua_tointeger(L, 1); - if (id < 0 || size_t(id) >= indices->countBlockDefs()) { + if (static_cast(id) >= indices->countBlockDefs()) { return 0; } auto def = indices->getBlockDef(id); @@ -60,18 +60,19 @@ int l_set_block(lua_State* L) { lua_Integer y = lua_tointeger(L, 2); lua_Integer z = lua_tointeger(L, 3); lua_Integer id = lua_tointeger(L, 4); - lua_Integer states = lua_tointeger(L, 5); + lua_Integer state = lua_tointeger(L, 5); bool noupdate = lua_toboolean(L, 6); - if (id < 0 || size_t(id) >= scripting::indices->countBlockDefs()) { + if (static_cast(id) >= scripting::indices->countBlockDefs()) { return 0; } if (!scripting::level->chunks->get(x, y, z)) { return 0; } - scripting::level->chunks->set(x, y, z, id, states); + scripting::level->chunks->set(x, y, z, id, int2blockstate(state)); scripting::level->lighting->onBlockSet(x,y,z, id); - if (!noupdate) + if (!noupdate) { scripting::blocks->updateSides(x, y, z); + } return 0; } @@ -97,7 +98,7 @@ int l_get_block_x(lua_State* L) { if (!def->rotatable) { return lua::pushivec3(L, 1, 0, 0); } else { - const CoordSystem& rot = def->rotations.variants[vox->rotation()]; + const CoordSystem& rot = def->rotations.variants[vox->state.rotation]; return lua::pushivec3(L, rot.axisX.x, rot.axisX.y, rot.axisX.z); } } @@ -114,7 +115,7 @@ int l_get_block_y(lua_State* L) { if (!def->rotatable) { return lua::pushivec3(L, 0, 1, 0); } else { - const CoordSystem& rot = def->rotations.variants[vox->rotation()]; + const CoordSystem& rot = def->rotations.variants[vox->state.rotation]; return lua::pushivec3(L, rot.axisY.x, rot.axisY.y, rot.axisY.z); } } @@ -131,7 +132,7 @@ int l_get_block_z(lua_State* L) { if (!def->rotatable) { return lua::pushivec3(L, 0, 0, 1); } else { - const CoordSystem& rot = def->rotations.variants[vox->rotation()]; + const CoordSystem& rot = def->rotations.variants[vox->state.rotation]; return lua::pushivec3(L, rot.axisZ.x, rot.axisZ.y, rot.axisZ.z); } } @@ -141,7 +142,7 @@ int l_get_block_rotation(lua_State* L) { lua_Integer y = lua_tointeger(L, 2); lua_Integer z = lua_tointeger(L, 3); voxel* vox = scripting::level->chunks->get(x, y, z); - int rotation = vox == nullptr ? 0 : vox->rotation(); + int rotation = vox == nullptr ? 0 : vox->state.rotation; lua_pushinteger(L, rotation); return 1; } @@ -155,8 +156,8 @@ int l_set_block_rotation(lua_State* L) { if (vox == nullptr) { return 0; } - vox->setRotation(value); - scripting::level->chunks->getChunkByVoxel(x, y, z)->setModified(true); + vox->state.rotation = value; + scripting::level->chunks->getChunkByVoxel(x, y, z)->setModifiedAndUnsaved(); return 0; } @@ -165,7 +166,7 @@ int l_get_block_states(lua_State* L) { lua_Integer y = lua_tointeger(L, 2); lua_Integer z = lua_tointeger(L, 3); voxel* vox = scripting::level->chunks->get(x, y, z); - int states = vox == nullptr ? 0 : vox->states; + int states = vox == nullptr ? 0 : blockstate2int(vox->state); lua_pushinteger(L, states); return 1; } @@ -181,8 +182,8 @@ int l_set_block_states(lua_State* L) { return 0; } voxel* vox = scripting::level->chunks->get(x, y, z); - vox->states = states; - chunk->setModified(true); + vox->state = int2blockstate(states); + chunk->setModifiedAndUnsaved(); return 0; } @@ -199,7 +200,7 @@ int l_get_block_user_bits(lua_State* L) { return 1; } uint mask = ((1 << bits) - 1) << offset; - uint data = (vox->states & mask) >> offset; + uint data = (blockstate2int(vox->state) & mask) >> offset; lua_pushinteger(L, data); return 1; } @@ -208,18 +209,23 @@ int l_set_block_user_bits(lua_State* L) { lua_Integer x = lua_tointeger(L, 1); lua_Integer y = lua_tointeger(L, 2); lua_Integer z = lua_tointeger(L, 3); - lua_Integer offset = lua_tointeger(L, 4) + VOXEL_USER_BITS_OFFSET; + lua_Integer offset = lua_tointeger(L, 4); lua_Integer bits = lua_tointeger(L, 5); size_t mask = ((1 << bits) - 1) << offset; lua_Integer value = (lua_tointeger(L, 6) << offset) & mask; + Chunk* chunk = scripting::level->chunks->getChunkByVoxel(x, y, z); + if (chunk == nullptr) { + return 0; + } voxel* vox = scripting::level->chunks->get(x, y, z); if (vox == nullptr) { return 0; } - vox->states = (vox->states & (~mask)) | value; - return 0; + vox->state.userbits = (vox->state.userbits & (~mask)) | value; + chunk->setModifiedAndUnsaved(); + return 0; } int l_is_replaceable_at(lua_State* L) { @@ -231,10 +237,22 @@ int l_is_replaceable_at(lua_State* L) { return 1; } +int l_block_caption(lua_State* L) { + auto indices = scripting::content->getIndices(); + lua_Integer id = lua_tointeger(L, 1); + if (static_cast(id) >= indices->countBlockDefs()) { + return 0; + } + auto def = indices->getBlockDef(id); + lua_pushstring(L, def->caption.c_str()); + return 1; +} + const luaL_Reg blocklib [] = { {"index", lua_wrap_errors}, {"name", lua_wrap_errors}, {"material", lua_wrap_errors}, + {"caption", lua_wrap_errors}, {"defs_count", lua_wrap_errors}, {"is_solid_at", lua_wrap_errors}, {"is_replaceable_at", lua_wrap_errors}, diff --git a/src/logic/scripting/lua/libcore.cpp b/src/logic/scripting/lua/libcore.cpp index 86b4e9f6..b9df74f2 100644 --- a/src/logic/scripting/lua/libcore.cpp +++ b/src/logic/scripting/lua/libcore.cpp @@ -100,19 +100,6 @@ static int l_reconfig_packs(lua_State* L) { return 0; } -static int l_get_bindings(lua_State* L) { - auto& bindings = Events::bindings; - lua_createtable(L, bindings.size(), 0); - - int i = 0; - for (auto& entry : bindings) { - lua_pushstring(L, entry.first.c_str()); - lua_rawseti(L, -2, i + 1); - i++; - } - return 1; -} - static int l_get_setting(lua_State* L) { auto name = lua_tostring(L, 1); const auto value = scripting::engine->getSettingsHandler().getValue(name); @@ -186,7 +173,6 @@ const luaL_Reg corelib [] = { {"close_world", lua_wrap_errors}, {"delete_world", lua_wrap_errors}, {"reconfig_packs", lua_wrap_errors}, - {"get_bindings", lua_wrap_errors}, {"get_setting", lua_wrap_errors}, {"set_setting", lua_wrap_errors}, {"str_setting", lua_wrap_errors}, diff --git a/src/logic/scripting/lua/libfile.cpp b/src/logic/scripting/lua/libfile.cpp index b14a15b4..6b97da3c 100644 --- a/src/logic/scripting/lua/libfile.cpp +++ b/src/logic/scripting/lua/libfile.cpp @@ -1,5 +1,6 @@ #include "lua_commons.hpp" #include "api_lua.hpp" +#include "LuaState.hpp" #include "../scripting.hpp" #include "../../../engine.hpp" #include "../../../files/files.hpp" @@ -11,24 +12,41 @@ namespace fs = std::filesystem; -static fs::path resolve_path(lua_State*, const std::string& path) { - return scripting::engine->getPaths()->resolve(path); +namespace scripting { + extern lua::LuaState* state; +} + +using namespace scripting; + +static fs::path resolve_path(const std::string& path) { + return engine->getPaths()->resolve(path); +} + +static fs::path resolve_path_soft(const std::string& path) { + if (path.find(':') == std::string::npos) { + return path; + } + return engine->getPaths()->resolve(path, false); } static int l_file_find(lua_State* L) { - std::string path = lua_tostring(L, 1); - lua_pushstring(L, scripting::engine->getResPaths()->findRaw(path).c_str()); - return 1; + std::string path = state->requireString(1); + try { + lua_pushstring(L, engine->getResPaths()->findRaw(path).c_str()); + return 1; + } catch (const std::runtime_error& err) { + return 0; + } } static int l_file_resolve(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); + fs::path path = resolve_path(state->requireString(1)); lua_pushstring(L, path.u8string().c_str()); return 1; } static int l_file_read(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); + fs::path path = resolve_path(state->requireString(1)); if (fs::is_regular_file(path)) { lua_pushstring(L, files::read_string(path).c_str()); return 1; @@ -36,55 +54,50 @@ static int l_file_read(lua_State* L) { throw std::runtime_error("file does not exists "+util::quote(path.u8string())); } -static int l_file_write(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); - const char* text = lua_tostring(L, 2); +static int l_file_write(lua_State*) { + fs::path path = resolve_path(state->requireString(1)); + auto text = state->requireString(2); files::write_string(path, text); return 1; } -static int l_file_remove(lua_State* L) { - std::string rawpath = lua_tostring(L, 1); - fs::path path = resolve_path(L, rawpath); +static int l_file_remove(lua_State*) { + std::string rawpath = state->requireString(1); + fs::path path = resolve_path(rawpath); auto entryPoint = rawpath.substr(0, rawpath.find(':')); if (entryPoint != "world") { throw std::runtime_error("access denied"); } - lua_pushboolean(L, fs::remove(path)); - return 1; + return state->pushboolean(fs::remove(path)); } -static int l_file_remove_tree(lua_State* L) { - std::string rawpath = lua_tostring(L, 1); - fs::path path = resolve_path(L, rawpath); +static int l_file_remove_tree(lua_State*) { + std::string rawpath = state->requireString(1); + fs::path path = resolve_path(rawpath); auto entryPoint = rawpath.substr(0, rawpath.find(':')); if (entryPoint != "world") { throw std::runtime_error("access denied"); } - lua_pushinteger(L, fs::remove_all(path)); - return 1; + return state->pushinteger(fs::remove_all(path)); } -static int l_file_exists(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); - lua_pushboolean(L, fs::exists(path)); - return 1; +static int l_file_exists(lua_State*) { + fs::path path = resolve_path_soft(state->requireString(1)); + return state->pushboolean(fs::exists(path)); } -static int l_file_isfile(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); - lua_pushboolean(L, fs::is_regular_file(path)); - return 1; +static int l_file_isfile(lua_State*) { + fs::path path = resolve_path_soft(state->requireString(1)); + return state->pushboolean(fs::is_regular_file(path)); } -static int l_file_isdir(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); - lua_pushboolean(L, fs::is_directory(path)); - return 1; +static int l_file_isdir(lua_State*) { + fs::path path = resolve_path_soft(state->requireString(1)); + return state->pushboolean(fs::is_directory(path)); } static int l_file_length(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); + fs::path path = resolve_path(state->requireString(1)); if (fs::exists(path)){ lua_pushinteger(L, fs::file_size(path)); } else { @@ -94,19 +107,19 @@ static int l_file_length(lua_State* L) { } static int l_file_mkdir(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); + fs::path path = resolve_path(state->requireString(1)); lua_pushboolean(L, fs::create_directory(path)); return 1; } static int l_file_mkdirs(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); + fs::path path = resolve_path(state->requireString(1)); lua_pushboolean(L, fs::create_directories(path)); return 1; } static int l_file_read_bytes(lua_State* L) { - fs::path path = resolve_path(L, lua_tostring(L, 1)); + fs::path path = resolve_path(state->requireString(1)); if (fs::is_regular_file(path)) { size_t length = static_cast(fs::file_size(path)); @@ -148,7 +161,7 @@ static int l_file_write_bytes(lua_State* L) { throw std::runtime_error("string expected"); } - fs::path path = resolve_path(L, lua_tostring(L, pathIndex)); + fs::path path = resolve_path(state->requireString(pathIndex)); std::vector bytes; @@ -163,7 +176,7 @@ static int l_file_write_bytes(lua_State* L) { } static int l_file_list_all_res(lua_State* L, const std::string& path) { - auto files = scripting::engine->getResPaths()->listdirRaw(path); + auto files = engine->getResPaths()->listdirRaw(path); lua_createtable(L, files.size(), 0); for (size_t i = 0; i < files.size(); i++) { lua_pushstring(L, files[i].c_str()); @@ -173,11 +186,11 @@ static int l_file_list_all_res(lua_State* L, const std::string& path) { } static int l_file_list(lua_State* L) { - std::string dirname = lua_tostring(L, 1); + std::string dirname = state->requireString(1); if (dirname.find(':') == std::string::npos) { return l_file_list_all_res(L, dirname); } - fs::path path = resolve_path(L, dirname); + fs::path path = resolve_path(dirname); if (!fs::is_directory(path)) { throw std::runtime_error(util::quote(path.u8string())+" is not a directory"); } diff --git a/src/logic/scripting/lua/libgui.cpp b/src/logic/scripting/lua/libgui.cpp index b4f4d9c3..3c738dde 100644 --- a/src/logic/scripting/lua/libgui.cpp +++ b/src/logic/scripting/lua/libgui.cpp @@ -93,6 +93,18 @@ static int l_container_add(lua_State* L) { return 0; } +static int l_node_destruct(lua_State* L) { + auto docnode = getDocumentNode(L); + auto node = std::dynamic_pointer_cast(docnode.node); + engine->getGUI()->postRunnable([node]() { + auto parent = node->getParent(); + if (auto container = dynamic_cast(parent)) { + container->remove(node); + } + }); + return 0; +} + static int l_container_clear(lua_State* L) { auto node = getDocumentNode(L, 1); if (auto container = std::dynamic_pointer_cast(node.node)) { @@ -101,6 +113,17 @@ static int l_container_clear(lua_State* L) { return 0; } +static int l_container_set_interval(lua_State* L) { + auto node = getDocumentNode(L, 1); + auto interval = state->tointeger(2) / 1000.0f; + if (auto container = std::dynamic_pointer_cast(node.node)) { + state->pushvalue(3); + auto runnable = state->createRunnable(); + container->listenInterval(interval, runnable); + } + return 0; +} + static int l_move_into(lua_State* L) { auto node = getDocumentNode(L, 1); auto dest = getDocumentNode(L, 2); @@ -243,14 +266,25 @@ static int p_get_src(UINode* node) { static int p_get_add(UINode* node) { if (dynamic_cast(node)) { - return state->pushcfunction(l_container_add); + return state->pushcfunction(lua_wrap_errors); } return 0; } +static int p_get_destruct(UINode*) { + return state->pushcfunction(lua_wrap_errors); +} + static int p_get_clear(UINode* node) { if (dynamic_cast(node)) { - return state->pushcfunction(l_container_clear); + return state->pushcfunction(lua_wrap_errors); + } + return 0; +} + +static int p_set_interval(UINode* node) { + if (dynamic_cast(node)) { + return state->pushcfunction(lua_wrap_errors); } return 0; } @@ -313,10 +347,12 @@ static int l_gui_getattr(lua_State* L) { {"size", p_get_size}, {"interactive", p_is_interactive}, {"visible", p_is_visible}, - {"enabled", p_is_enabled}, + {"enabled", p_is_enabled}, {"move_into", p_move_into}, {"add", p_get_add}, + {"destruct", p_get_destruct}, {"clear", p_get_clear}, + {"setInterval", p_set_interval}, {"placeholder", p_get_placeholder}, {"valid", p_is_valid}, {"caret", p_get_caret}, diff --git a/src/logic/scripting/lua/libinput.cpp b/src/logic/scripting/lua/libinput.cpp index beb2c5de..309cc26b 100644 --- a/src/logic/scripting/lua/libinput.cpp +++ b/src/logic/scripting/lua/libinput.cpp @@ -25,16 +25,23 @@ static int l_keycode(lua_State* L) { return 1; } -static int l_add_callback(lua_State* L) { +static int l_mousecode(lua_State* L) { + const char* name = state->requireString(1); + lua_pushinteger(L, static_cast(input_util::mousecode_from(name))); + return 1; +} + +static int l_add_callback(lua_State*) { auto bindname = state->requireString(1); const auto& bind = Events::bindings.find(bindname); if (bind == Events::bindings.end()) { throw std::runtime_error("unknown binding "+util::quote(bindname)); } state->pushvalue(2); + runnable actual_callback = state->createRunnable(); runnable callback = [=]() { if (!scripting::engine->getGUI()->isFocusCaught()) { - state->createRunnable(); + actual_callback(); } }; if (hud) { @@ -49,10 +56,25 @@ static int l_get_mouse_pos(lua_State* L) { return lua::pushvec2_arr(L, Events::cursor); } +static int l_get_bindings(lua_State* L) { + auto& bindings = Events::bindings; + lua_createtable(L, bindings.size(), 0); + + int i = 0; + for (auto& entry : bindings) { + lua_pushstring(L, entry.first.c_str()); + lua_rawseti(L, -2, i + 1); + i++; + } + return 1; +} + const luaL_Reg inputlib [] = { {"keycode", lua_wrap_errors}, + {"mousecode", lua_wrap_errors}, {"add_callback", lua_wrap_errors}, {"get_mouse_pos", lua_wrap_errors}, + {"get_bindings", lua_wrap_errors}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libjson.cpp b/src/logic/scripting/lua/libjson.cpp index d868d2da..7d3be119 100644 --- a/src/logic/scripting/lua/libjson.cpp +++ b/src/logic/scripting/lua/libjson.cpp @@ -23,14 +23,14 @@ static int l_json_stringify(lua_State* L) { } static int l_json_parse(lua_State* L) { - auto string = lua_tostring(L, 1); + auto string = scripting::state->requireString(1); auto element = json::parse("", string); scripting::state->pushvalue(element); return 1; } const luaL_Reg jsonlib [] = { - {"stringify", lua_wrap_errors}, + {"tostring", lua_wrap_errors}, {"parse", lua_wrap_errors}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libplayer.cpp b/src/logic/scripting/lua/libplayer.cpp index 3ce71759..045aae37 100644 --- a/src/logic/scripting/lua/libplayer.cpp +++ b/src/logic/scripting/lua/libplayer.cpp @@ -132,6 +132,20 @@ static int l_player_set_noclip(lua_State* L) { return 0; } +static int l_player_get_selected_block(lua_State* L) { + if (auto player = get_player(L, 1)) { + if (player->selectedVoxel.id == BLOCK_VOID) { + return 0; + } + const glm::ivec3 pos = player->selectedBlockPosition; + lua_pushinteger(L, pos.x); + lua_pushinteger(L, pos.y); + lua_pushinteger(L, pos.z); + return 3; + } + return 0; +} + const luaL_Reg playerlib [] = { {"get_pos", lua_wrap_errors}, {"set_pos", lua_wrap_errors}, @@ -144,5 +158,6 @@ const luaL_Reg playerlib [] = { {"set_flight", lua_wrap_errors}, {"is_noclip", lua_wrap_errors}, {"set_noclip", lua_wrap_errors}, + {"get_selected_block", lua_wrap_errors}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libtoml.cpp b/src/logic/scripting/lua/libtoml.cpp index 4bb4a123..86411d42 100644 --- a/src/logic/scripting/lua/libtoml.cpp +++ b/src/logic/scripting/lua/libtoml.cpp @@ -31,7 +31,7 @@ static int l_toml_parse(lua_State*) { } const luaL_Reg tomllib [] = { - {"serialize", lua_wrap_errors}, - {"deserialize", lua_wrap_errors}, + {"tostring", lua_wrap_errors}, + {"parse", lua_wrap_errors}, {NULL, NULL} }; diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 3dd9e4be..0afeda5c 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -44,8 +44,9 @@ public: std::shared_ptr currentCamera; std::unique_ptr hitbox; bool debug = false; - voxel selectedVoxel {0, 0}; + voxel selectedVoxel {0, {}}; glm::vec3 cam {}; + glm::ivec3 selectedBlockPosition {}; Player(glm::vec3 position, float speed, std::shared_ptr inv); ~Player(); diff --git a/src/physics/PhysicsSolver.cpp b/src/physics/PhysicsSolver.cpp index 6760aa89..4370be30 100644 --- a/src/physics/PhysicsSolver.cpp +++ b/src/physics/PhysicsSolver.cpp @@ -33,6 +33,7 @@ void PhysicsSolver::step( hitbox->grounded = false; for (uint i = 0; i < substeps; i++) { float px = pos.x; + float py = pos.y; float pz = pos.z; vel += gravity * dt * gravityScale; @@ -44,6 +45,9 @@ void PhysicsSolver::step( vel.z *= glm::max(0.0f, 1.0f - dt * linear_damping); pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f; + if (hitbox->grounded) { + pos.y = py; + } if (shifting && hitbox->grounded){ float y = (pos.y-half.y-E); @@ -219,21 +223,19 @@ bool PhysicsSolver::isBlockInside(int x, int y, int z, Hitbox* hitbox) { y >= floor(pos.y-half.y) && y <= floor(pos.y+half.y); } -bool PhysicsSolver::isBlockInside(int x, int y, int z, Block* def, blockstate_t states, Hitbox* hitbox) { +bool PhysicsSolver::isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox) { + const float E = 0.001f; // inaccuracy const glm::vec3& pos = hitbox->position; const glm::vec3& half = hitbox->halfsize; - voxel v {}; - v.states = states; const auto& boxes = def->rotatable - ? def->rt.hitboxes[v.rotation()] + ? def->rt.hitboxes[state.rotation] : def->hitboxes; for (const auto& block_hitbox : boxes) { glm::vec3 min = block_hitbox.min(); glm::vec3 max = block_hitbox.max(); - // 0.00001 - inaccuracy - if (min.x < pos.x+half.x-x-0.00001f && max.x > pos.x-half.x-x+0.00001f && - min.z < pos.z+half.z-z-0.00001f && max.z > pos.z-half.z-z+0.00001f && - min.y < pos.y+half.y-y-0.00001f && max.y > pos.y-half.y-y+0.00001f) + if (min.x < pos.x+half.x-x-E && max.x > pos.x-half.x-x+E && + min.z < pos.z+half.z-z-E && max.z > pos.z-half.z-z+E && + min.y < pos.y+half.y-y-E && max.y > pos.y-half.y-y+E) return true; } return false; diff --git a/src/physics/PhysicsSolver.hpp b/src/physics/PhysicsSolver.hpp index e35f364c..6e940f99 100644 --- a/src/physics/PhysicsSolver.hpp +++ b/src/physics/PhysicsSolver.hpp @@ -2,6 +2,7 @@ #define PHYSICS_PHYSICSSOLVER_HPP_ #include "../typedefs.hpp" +#include "../voxels/voxel.hpp" #include @@ -31,7 +32,7 @@ public: float stepHeight ); bool isBlockInside(int x, int y, int z, Hitbox* hitbox); - bool isBlockInside(int x, int y, int z, Block* def, blockstate_t states, Hitbox* hitbox); + bool isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox); }; #endif // PHYSICS_PHYSICSSOLVER_HPP_ diff --git a/src/settings.hpp b/src/settings.hpp index 165dcbec..250d0e4b 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -57,7 +57,7 @@ struct GraphicsSettings { /// 1.0 is linear, 2.0 is quadratic NumberSetting fogCurve {1.6f, 1.0f, 6.0f}; /// @brief Lighting gamma - NumberSetting gamma {1.0f, 0.5f, 2.0f}; + NumberSetting gamma {1.0f, 0.4f, 1.0f}; /// @brief Enable blocks backlight to prevent complete darkness FlagSetting backlight {true}; /// @brief Enable chunks frustum culling diff --git a/src/voxel_engine.cpp b/src/voxel_engine.cpp index 33d18c0e..c6a2ec7e 100644 --- a/src/voxel_engine.cpp +++ b/src/voxel_engine.cpp @@ -5,7 +5,6 @@ #include "util/platform.hpp" #include "util/command_line.hpp" #include "debug/Logger.hpp" -#include "objects/Player.hpp" #include @@ -13,7 +12,6 @@ static debug::Logger logger("main"); int main(int argc, char** argv) { debug::Logger::init("latest.log"); - std::cout << sizeof(PlayerInput) << std::endl; EnginePaths paths; if (!parse_cmdline(argc, argv, paths)) diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 0c4b3b68..4628dd0e 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -25,8 +25,8 @@ inline constexpr uint BLOCK_AABB_GRID = 16; inline std::string DEFAULT_MATERIAL = "base:stone"; struct block_funcs_set { - bool init: 1; - bool update: 1; + bool init: 1; + bool update: 1; bool onplaced: 1; bool onbroken: 1; bool oninteract: 1; @@ -35,46 +35,46 @@ struct block_funcs_set { }; struct CoordSystem { - glm::ivec3 axisX; - glm::ivec3 axisY; - glm::ivec3 axisZ; + glm::ivec3 axisX; + glm::ivec3 axisY; + glm::ivec3 axisZ; - /// @brief Grid 3d position fix offset (for negative vectors) - glm::ivec3 fix; + /// @brief Grid 3d position fix offset (for negative vectors) + glm::ivec3 fix; - CoordSystem() = default; - CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ); + CoordSystem() = default; + CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ); - void transform(AABB& aabb) const; + void transform(AABB& aabb) const; - inline bool isVectorHasNegatives(glm::ivec3 vec) { - return (vec.x < 0 || vec.y < 0 || vec.z < 0); - } + inline bool isVectorHasNegatives(glm::ivec3 vec) { + return (vec.x < 0 || vec.y < 0 || vec.z < 0); + } }; struct BlockRotProfile { - static const int MAX_COUNT = 8; - std::string name; - CoordSystem variants[MAX_COUNT]; + static const int MAX_COUNT = 8; + std::string name; + CoordSystem variants[MAX_COUNT]; - /// @brief Wood logs, pillars, pipes - static const BlockRotProfile PIPE; + /// @brief Wood logs, pillars, pipes + static const BlockRotProfile PIPE; - /// @brief Doors, signs and other panes - static const BlockRotProfile PANE; + /// @brief Doors, signs and other panes + static const BlockRotProfile PANE; }; enum class BlockModel { - /// @brief invisible - none, - /// @brief default cube shape - block, - /// @brief X-shape (grass) - xsprite, - /// @brief box shape sized as block hitbox - aabb, - /// @brief custom model defined in json - custom + /// @brief invisible + none, + /// @brief default cube shape + block, + /// @brief X-shape (grass) + xsprite, + /// @brief box shape sized as block hitbox + aabb, + /// @brief custom model defined in json + custom }; using BoxModel = AABB; @@ -82,61 +82,61 @@ using BoxModel = AABB; /// @brief Common kit of block properties applied to groups of blocks struct BlockMaterial { - std::string name; - std::string stepsSound {""}; - std::string placeSound {""}; - std::string breakSound {""}; + std::string name; + std::string stepsSound {""}; + std::string placeSound {""}; + std::string breakSound {""}; }; /// @brief Block properties definition class Block { public: - /// @brief Block string id (with prefix included) - std::string const name; + /// @brief Block string id (with prefix included) + std::string const name; - std::string caption; - + std::string caption; + /// @brief Textures set applied to block sides - std::string textureFaces[6]; // -x,x, -y,y, -z,z - - std::vector modelTextures = {}; - std::vector modelBoxes = {}; - std::vector modelExtraPoints = {}; //initially made for tetragons - std::vector modelUVs = {}; // boxes' tex-UVs also there + std::string textureFaces[6]; // -x,x, -y,y, -z,z + + std::vector modelTextures = {}; + std::vector modelBoxes = {}; + std::vector modelExtraPoints = {}; //initially made for tetragons + std::vector modelUVs = {}; // boxes' tex-UVs also there /// @brief id of used BlockMaterial, may specify non-existing material std::string material = DEFAULT_MATERIAL; - /// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive clouds) - uint8_t emission[4] {0, 0, 0, 0}; + /// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive clouds) + uint8_t emission[4] {0, 0, 0, 0}; - /// @brief Influences visible block sides for transparent blocks - uint8_t drawGroup = 0; - + /// @brief Influences visible block sides for transparent blocks + uint8_t drawGroup = 0; + /// @brief Block model type - BlockModel model = BlockModel::block; - + BlockModel model = BlockModel::block; + /// @brief Does the block passing lights into itself - bool lightPassing = false; - + bool lightPassing = false; + /// @brief Does the block passing top-down sky lights into itself - bool skyLightPassing = false; - + bool skyLightPassing = false; + /// @brief Is the block a physical obstacle - bool obstacle = true; - + bool obstacle = true; + /// @brief Can the block be selected - bool selectable = true; - + bool selectable = true; + /// @brief Can the block be replaced with other. - /// Examples of replaceable blocks: air, flower, water - bool replaceable = false; - + /// Examples of replaceable blocks: air, flower, water + bool replaceable = false; + /// @brief Can player destroy the block - bool breakable = true; - + bool breakable = true; + /// @brief Can the block be oriented different ways - bool rotatable = false; + bool rotatable = false; /// @brief Can the block exist without physical support be a solid block below bool grounded = false; @@ -146,46 +146,49 @@ public: /// @brief Set of block physical hitboxes std::vector hitboxes; - + /// @brief Set of available block rotations (coord-systems) - BlockRotProfile rotations; + BlockRotProfile rotations; /// @brief Item will be picked on MMB click on the block std::string pickingItem = name+BLOCK_ITEM_SUFFIX; /// @brief Block script name in blocks/ without extension std::string scriptName = name.substr(name.find(':')+1); - + /// @brief Default block layout will be used by hud.open_block(...) - std::string uiLayout = name; + std::string uiLayout = name; /// @brief Block inventory size. 0 - no inventory uint inventorySize = 0; - /// @brief Runtime indices (content indexing results) - struct { - /// @brief block runtime integer id - blockid_t id; - + // @brief Block tick interval (1 - 20tps, 2 - 10tps) + uint tickInterval = 1; + + /// @brief Runtime indices (content indexing results) + struct { + /// @brief block runtime integer id + blockid_t id; + /// @brief is the block completely opaque for render and raycast - bool solid = true; - + bool solid = true; + /// @brief does the block emit any lights - bool emissive = false; + bool emissive = false; /// @brief set of hitboxes sets with all coord-systems precalculated std::vector hitboxes[BlockRotProfile::MAX_COUNT]; - + /// @brief set of block callbacks flags - block_funcs_set funcsset {}; + block_funcs_set funcsset {}; /// @brief picking item integer id itemid_t pickingItem = 0; - } rt; + } rt; - Block(std::string name); - Block(std::string name, std::string texture); - Block(const Block&) = delete; + Block(std::string name); + Block(std::string name, std::string texture); + Block(const Block&) = delete; }; #endif /* VOXELS_BLOCK_HPP_ */ diff --git a/src/voxels/Chunk.cpp b/src/voxels/Chunk.cpp index bfd79cda..18dabec2 100644 --- a/src/voxels/Chunk.cpp +++ b/src/voxels/Chunk.cpp @@ -7,56 +7,52 @@ #include "../lighting/Lightmap.hpp" Chunk::Chunk(int xpos, int zpos) : x(xpos), z(zpos){ - bottom = 0; - top = CHUNK_H; - for (uint i = 0; i < CHUNK_VOL; i++) { - voxels[i].id = 2; - voxels[i].states = 0; - } + bottom = 0; + top = CHUNK_H; } bool Chunk::isEmpty(){ - int id = -1; - for (uint i = 0; i < CHUNK_VOL; i++){ - if (voxels[i].id != id){ - if (id != -1) - return false; - else - id = voxels[i].id; - } - } - return true; + int id = -1; + for (uint i = 0; i < CHUNK_VOL; i++){ + if (voxels[i].id != id){ + if (id != -1) + return false; + else + id = voxels[i].id; + } + } + return true; } void Chunk::updateHeights() { - for (uint i = 0; i < CHUNK_VOL; i++) { - if (voxels[i].id != 0) { - bottom = i / (CHUNK_D * CHUNK_W); - break; - } - } - for (int i = CHUNK_VOL - 1; i >= 0; i--) { - if (voxels[i].id != 0) { - top = i / (CHUNK_D * CHUNK_W) + 1; - break; - } - } + for (uint i = 0; i < CHUNK_VOL; i++) { + if (voxels[i].id != 0) { + bottom = i / (CHUNK_D * CHUNK_W); + break; + } + } + for (int i = CHUNK_VOL - 1; i >= 0; i--) { + if (voxels[i].id != 0) { + top = i / (CHUNK_D * CHUNK_W) + 1; + break; + } + } } void Chunk::addBlockInventory(std::shared_ptr inventory, uint x, uint y, uint z) { inventories[vox_index(x, y, z)] = inventory; - setUnsaved(true); + flags.unsaved = true; } void Chunk::removeBlockInventory(uint x, uint y, uint z) { - if (inventories.erase(vox_index(x, y, z))) { - setUnsaved(true); - } + if (inventories.erase(vox_index(x, y, z))) { + flags.unsaved = true; + } } void Chunk::setBlockInventories(chunk_inventories_map map) { - inventories = map; + inventories = map; } std::shared_ptr Chunk::getBlockInventory(uint x, uint y, uint z) const { @@ -70,12 +66,12 @@ std::shared_ptr Chunk::getBlockInventory(uint x, uint y, uint z) cons } std::unique_ptr Chunk::clone() const { - auto other = std::make_unique(x,z); - for (uint i = 0; i < CHUNK_VOL; i++) { - other->voxels[i] = voxels[i]; + auto other = std::make_unique(x,z); + for (uint i = 0; i < CHUNK_VOL; i++) { + other->voxels[i] = voxels[i]; } - other->lightmap.set(&lightmap); - return other; + other->lightmap.set(&lightmap); + return other; } /** @@ -93,19 +89,21 @@ std::unique_ptr Chunk::clone() const { Total size: (CHUNK_VOL * 4) bytes */ std::unique_ptr Chunk::encode() const { - auto buffer = std::make_unique(CHUNK_DATA_LEN); - for (uint i = 0; i < CHUNK_VOL; i++) { - buffer[i] = voxels[i].id >> 8; + auto buffer = std::make_unique(CHUNK_DATA_LEN); + for (uint i = 0; i < CHUNK_VOL; i++) { + buffer[i] = voxels[i].id >> 8; buffer[CHUNK_VOL+i] = voxels[i].id & 0xFF; - buffer[CHUNK_VOL*2 + i] = voxels[i].states >> 8; - buffer[CHUNK_VOL*3 + i] = voxels[i].states & 0xFF; - } - return buffer; + + blockstate_t state = blockstate2int(voxels[i].state); + buffer[CHUNK_VOL*2 + i] = state >> 8; + buffer[CHUNK_VOL*3 + i] = state & 0xFF; + } + return buffer; } bool Chunk::decode(const ubyte* data) { - for (uint i = 0; i < CHUNK_VOL; i++) { - voxel& vox = voxels[i]; + for (uint i = 0; i < CHUNK_VOL; i++) { + voxel& vox = voxels[i]; ubyte bid1 = data[i]; ubyte bid2 = data[CHUNK_VOL + i]; @@ -113,10 +111,10 @@ bool Chunk::decode(const ubyte* data) { ubyte bst1 = data[CHUNK_VOL*2 + i]; ubyte bst2 = data[CHUNK_VOL*3 + i]; - vox.id = (blockid_t(bid1) << 8) | (blockid_t(bid2)); - vox.states = (blockstate_t(bst1) << 8) | (blockstate_t(bst2)); - } - return true; + vox.id = (blockid_t(bid1) << 8) | (blockid_t(bid2)); + vox.state = int2blockstate((blockstate_t(bst1) << 8) | (blockstate_t(bst2))); + } + return true; } void Chunk::convert(ubyte* data, const ContentLUT* lut) { diff --git a/src/voxels/Chunk.hpp b/src/voxels/Chunk.hpp index 019b4f09..535b6125 100644 --- a/src/voxels/Chunk.hpp +++ b/src/voxels/Chunk.hpp @@ -9,14 +9,6 @@ #include "voxel.hpp" #include "../lighting/Lightmap.hpp" -struct ChunkFlag { - static const int MODIFIED = 0x1; - static const int READY = 0x2; - static const int LOADED = 0x4; - static const int LIGHTED = 0x8; - static const int UNSAVED = 0x10; - static const int LOADED_LIGHTS = 0x20; -}; inline constexpr int CHUNK_DATA_LEN = CHUNK_VOL*4; class Lightmap; @@ -27,72 +19,52 @@ using chunk_inventories_map = std::unordered_map clone() const; - - // flags getters/setters below - inline void setFlags(int mask, bool value){ - if (value) - flags |= mask; - else - flags &= ~(mask); - } + std::unique_ptr clone() const; /* Creates new block inventory given size @return inventory id or 0 if block does not exists */ void addBlockInventory(std::shared_ptr inventory, uint x, uint y, uint z); - void removeBlockInventory(uint x, uint y, uint z); - void setBlockInventories(chunk_inventories_map map); + void removeBlockInventory(uint x, uint y, uint z); + void setBlockInventories(chunk_inventories_map map); /* @return inventory bound to the given block or nullptr */ std::shared_ptr getBlockInventory(uint x, uint y, uint z) const; - inline bool isUnsaved() const {return flags & ChunkFlag::UNSAVED;} + inline void setModifiedAndUnsaved() { + flags.modified = true; + flags.unsaved = true; + } - inline bool isModified() const {return flags & ChunkFlag::MODIFIED;} - - inline bool isLighted() const {return flags & ChunkFlag::LIGHTED;} - - inline bool isLoaded() const {return flags & ChunkFlag::LOADED;} - - inline bool isLoadedLights() const {return flags & ChunkFlag::LOADED_LIGHTS;} - - inline bool isReady() const {return flags & ChunkFlag::READY;} - - inline void setUnsaved(bool newState) {setFlags(ChunkFlag::UNSAVED, newState);} - - inline void setModified(bool newState) {setFlags(ChunkFlag::MODIFIED, newState);} - - inline void setLoaded(bool newState) {setFlags(ChunkFlag::LOADED, newState);} - - inline void setLoadedLights(bool newState) {setFlags(ChunkFlag::LOADED_LIGHTS, newState);} - - inline void setLighted(bool newState) {setFlags(ChunkFlag::LIGHTED, newState);} - - inline void setReady(bool newState) {setFlags(ChunkFlag::READY, newState);} - - std::unique_ptr encode() const; + std::unique_ptr encode() const; /** * @return true if all is fine **/ - bool decode(const ubyte* data); + bool decode(const ubyte* data); static void convert(ubyte* data, const ContentLUT* lut); }; diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index 63919499..b9a63e9e 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -68,7 +68,7 @@ const AABB* Chunks::isObstacleAt(float x, float y, float z){ const Block* def = contentIds->getBlockDef(v->id); if (def->obstacle) { const auto& boxes = def->rotatable - ? def->rt.hitboxes[v->rotation()] + ? def->rt.hitboxes[v->state.rotation] : def->hitboxes; for (const auto& hitbox : boxes) { if (hitbox.contains({x - ix, y - iy, z - iz})) { @@ -158,7 +158,7 @@ Chunk* Chunks::getChunk(int x, int z){ return chunks[z * w + x].get(); } -void Chunks::set(int32_t x, int32_t y, int32_t z, uint32_t id, uint8_t states) { +void Chunks::set(int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state) { if (y < 0 || y >= CHUNK_H) return; x -= ox * CHUNK_W; @@ -178,24 +178,22 @@ void Chunks::set(int32_t x, int32_t y, int32_t z, uint32_t id, uint8_t states) { if (def->inventorySize == 0) chunk->removeBlockInventory(lx, y, lz); vox.id = id; - vox.states = states; - - chunk->setUnsaved(true); - chunk->setModified(true); + vox.state = state; + chunk->setModifiedAndUnsaved(); if (y < chunk->bottom) chunk->bottom = y; else if (y + 1 > chunk->top) chunk->top = y + 1; else if (id == 0) chunk->updateHeights(); if (lx == 0 && (chunk = getChunk(cx+ox-1, cz+oz))) - chunk->setModified(true); + chunk->flags.modified = true; if (lz == 0 && (chunk = getChunk(cx+ox, cz+oz-1))) - chunk->setModified(true); + chunk->flags.modified = true; if (lx == CHUNK_W-1 && (chunk = getChunk(cx+ox+1, cz+oz))) - chunk->setModified(true); + chunk->flags.modified = true; if (lz == CHUNK_D-1 && (chunk = getChunk(cx+ox, cz+oz+1))) - chunk->setModified(true); + chunk->flags.modified = true; } voxel* Chunks::rayCast( @@ -255,7 +253,7 @@ voxel* Chunks::rayCast( if (!def->rt.solid) { const std::vector& hitboxes = def->rotatable - ? def->rt.hitboxes[voxel->rotation()] + ? def->rt.hitboxes[voxel->state.rotation] : def->hitboxes; scalar_t distance = maxDist; @@ -365,7 +363,7 @@ glm::vec3 Chunks::rayCastToObstacle(glm::vec3 start, glm::vec3 dir, float maxDis if (def->obstacle) { if (!def->rt.solid) { const std::vector& hitboxes = def->rotatable - ? def->rt.hitboxes[voxel->rotation()] + ? def->rt.hitboxes[voxel->state.rotation] : def->modelBoxes; scalar_t distance; @@ -499,12 +497,12 @@ void Chunks::saveAndClear(){ for (size_t i = 0; i < volume; i++){ Chunk* chunk = chunks[i].get(); chunks[i] = nullptr; - if (chunk == nullptr || !chunk->isLighted()) + if (chunk == nullptr || !chunk->flags.lighted) continue; - bool lightsUnsaved = !chunk->isLoadedLights() && + bool lightsUnsaved = !chunk->flags.loadedLights && worldFiles->doesWriteLights(); - if (!chunk->isUnsaved() && !lightsUnsaved) + if (!chunk->flags.unsaved && !lightsUnsaved) continue; regions.put(chunk); } diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index d2eb8cea..7dd4312d 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -5,6 +5,8 @@ #include #include #include + +#include "voxel.hpp" #include "../typedefs.hpp" class VoxelRenderer; @@ -13,7 +15,6 @@ struct AABB; class Content; class ContentIndices; class Chunk; -struct voxel; class WorldFiles; class LevelEvents; @@ -42,7 +43,7 @@ public: voxel* get(int32_t x, int32_t y, int32_t z); light_t getLight(int32_t x, int32_t y, int32_t z); ubyte getLight(int32_t x, int32_t y, int32_t z, int channel); - void set(int32_t x, int32_t y, int32_t z, uint32_t id, uint8_t states); + void set(int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state); voxel* rayCast( glm::vec3 start, diff --git a/src/voxels/ChunksStorage.cpp b/src/voxels/ChunksStorage.cpp index fa28f4f6..8a9c97b4 100644 --- a/src/voxels/ChunksStorage.cpp +++ b/src/voxels/ChunksStorage.cpp @@ -61,7 +61,7 @@ std::shared_ptr ChunksStorage::create(int x, int z) { chunk->decode(data.get()); auto invs = regions.fetchInventories(chunk->x, chunk->z); chunk->setBlockInventories(std::move(invs)); - chunk->setLoaded(true); + chunk->flags.loaded = true; for(auto& entry : chunk->inventories) { level->inventories->store(entry.second); } @@ -71,7 +71,7 @@ std::shared_ptr ChunksStorage::create(int x, int z) { auto lights = regions.getLights(chunk->x, chunk->z); if (lights) { chunk->lightmap.set(lights.get()); - chunk->setLoadedLights(true); + chunk->flags.loadedLights = true; } return chunk; } diff --git a/src/voxels/DefaultWorldGenerator.cpp b/src/voxels/DefaultWorldGenerator.cpp index b0c8561b..49747519 100644 --- a/src/voxels/DefaultWorldGenerator.cpp +++ b/src/voxels/DefaultWorldGenerator.cpp @@ -163,7 +163,7 @@ void DefaultWorldGenerator::generate(voxel* voxels, int cx, int cz, int seed){ for (int cur_y = 0; cur_y < CHUNK_H; cur_y++){ // int cur_y = y; int id = cur_y < SEA_LEVEL ? idWater : BLOCK_AIR; - int states = 0; + blockstate state {}; if ((cur_y == (int)height) && (SEA_LEVEL-2 < cur_y)) { id = idGrassBlock; } else if (cur_y < (height - 6)){ @@ -177,7 +177,7 @@ void DefaultWorldGenerator::generate(voxel* voxels, int cx, int cz, int seed){ treesTile, idWood, idLeaves); if (tree) { id = tree; - states = BLOCK_DIR_UP; + state.rotation = BLOCK_DIR_UP; } } float sand = fmax(heights.get(MAPS::SAND, cur_x, cur_z), heights.get(MAPS::CLIFF, cur_x, cur_z)); @@ -198,10 +198,10 @@ void DefaultWorldGenerator::generate(voxel* voxels, int cx, int cz, int seed){ } if ((height > SEA_LEVEL+1) && ((int)(height + 1) == cur_y) && ((unsigned short)randomgrass.rand() > 65533)){ id = idWood; - states = BLOCK_DIR_UP; + state.rotation = BLOCK_DIR_UP; } voxels[(cur_y * CHUNK_D + z) * CHUNK_W + x].id = id; - voxels[(cur_y * CHUNK_D + z) * CHUNK_W + x].states = states; + voxels[(cur_y * CHUNK_D + z) * CHUNK_W + x].state = state; } } } diff --git a/src/voxels/FlatWorldGenerator.cpp b/src/voxels/FlatWorldGenerator.cpp index 8e5c440e..f729d32b 100644 --- a/src/voxels/FlatWorldGenerator.cpp +++ b/src/voxels/FlatWorldGenerator.cpp @@ -10,7 +10,7 @@ void FlatWorldGenerator::generate(voxel* voxels, int cx, int cz, int seed) { for (int x = 0; x < CHUNK_W; x++) { for (int cur_y = 0; cur_y < CHUNK_H; cur_y++){ int id = BLOCK_AIR; - int states = 0; + blockstate state {}; if(cur_y == 2) { id = idBazalt; @@ -21,7 +21,7 @@ void FlatWorldGenerator::generate(voxel* voxels, int cx, int cz, int seed) { } voxels[(cur_y * CHUNK_D + z) * CHUNK_W + x].id = id; - voxels[(cur_y * CHUNK_D + z) * CHUNK_W + x].states = states; + voxels[(cur_y * CHUNK_D + z) * CHUNK_W + x].state = state; } } } diff --git a/src/voxels/voxel.hpp b/src/voxels/voxel.hpp index db2c8138..9cfaf698 100644 --- a/src/voxels/voxel.hpp +++ b/src/voxels/voxel.hpp @@ -10,22 +10,34 @@ inline constexpr int BLOCK_DIR_EAST = 0x3; inline constexpr int BLOCK_DIR_UP = 0x4; inline constexpr int BLOCK_DIR_DOWN = 0x5; -// limited to 8 block orientations -inline constexpr int BLOCK_ROT_MASK = 0b0000'0111; -// reserved bits -inline constexpr int BLOCK_RESERVED_MASK = 0b1111'1000; +struct blockstate { + uint8_t rotation : 3; + uint8_t segment : 2; // planned to 0.22 + uint8_t reserved : 3; + uint8_t userbits : 8; +}; +static_assert (sizeof(blockstate) == 2); + +inline constexpr blockstate_t blockstate2int(blockstate b) { + return static_cast(b.rotation) | + static_cast(b.segment) << 3 | + static_cast(b.reserved) << 5 | + static_cast(b.userbits) << 8; +} + +inline constexpr blockstate int2blockstate(blockstate_t i) { + return { + static_cast(i & 0b111), + static_cast((i >> 3) & 0b11), + static_cast((i >> 5) & 0b111), + static_cast((i >> 8) & 0xFF) + }; +} struct voxel { blockid_t id; - blockstate_t states; - - inline uint8_t rotation() const { - return states & BLOCK_ROT_MASK; - } - - inline void setRotation(uint8_t rotation) { - states = (states & (~BLOCK_ROT_MASK)) | (rotation & BLOCK_ROT_MASK); - } + blockstate state; }; +static_assert(sizeof(voxel) == 4); #endif // VOXELS_VOXEL_HPP_ diff --git a/src/window/Events.cpp b/src/window/Events.cpp index 76158230..1aab7881 100644 --- a/src/window/Events.cpp +++ b/src/window/Events.cpp @@ -98,6 +98,14 @@ void Events::pollEvents() { } } +Binding& Events::getBinding(const std::string& name) { + auto found = bindings.find(name); + if (found == bindings.end()) { + throw std::runtime_error("binding '"+name+"' does not exists"); + } + return found->second; +} + void Events::bind(const std::string& name, inputtype type, keycode code) { bind(name, type, static_cast(code)); } @@ -110,6 +118,10 @@ void Events::bind(const std::string& name, inputtype type, int code) { bindings.emplace(name, Binding(type, code)); } +void Events::rebind(const std::string& name, inputtype type, int code) { + bindings[name] = Binding(type, code); +} + bool Events::active(const std::string& name) { const auto& found = bindings.find(name); if (found == bindings.end()) { @@ -155,19 +167,22 @@ std::string Events::writeBindings() { dynamic::Map obj; for (auto& entry : Events::bindings) { const auto& binding = entry.second; - - auto& jentry = obj.putMap(entry.first); + std::string value; switch (binding.type) { - case inputtype::keyboard: jentry.put("type", "keyboard"); break; - case inputtype::mouse: jentry.put("type", "mouse"); break; + case inputtype::keyboard: + value = "key:"+input_util::get_name(static_cast(binding.code)); + break; + case inputtype::mouse: + value = "mouse:"+input_util::get_name(static_cast(binding.code)); + break; default: throw std::runtime_error("unsupported control type"); } - jentry.put("code", binding.code); + obj.put(entry.first, value); } - return json::stringify(&obj, true, " "); + return toml::stringify(obj); } -void Events::loadBindings(const std::string& filename, const std::string& source) { +void Events::loadBindingsOld(const std::string& filename, const std::string& source) { auto obj = json::parse(filename, source); for (auto& entry : Events::bindings) { auto& binding = entry.second; @@ -192,7 +207,7 @@ void Events::loadBindings(const std::string& filename, const std::string& source } } -void Events::loadBindingsToml(const std::string& filename, const std::string& source) { +void Events::loadBindings(const std::string& filename, const std::string& source) { auto map = toml::parse(filename, source); for (auto& entry : map->values) { if (auto value = std::get_if(&entry.second)) { diff --git a/src/window/Events.hpp b/src/window/Events.hpp index c8d354d3..97405d2c 100644 --- a/src/window/Events.hpp +++ b/src/window/Events.hpp @@ -38,9 +38,11 @@ public: static void toggleCursor(); + static Binding& getBinding(const std::string& name); static void bind(const std::string& name, inputtype type, keycode code); static void bind(const std::string& name, inputtype type, mousecode code); static void bind(const std::string& name, inputtype type, int code); + static void rebind(const std::string& name, inputtype type, int code); static bool active(const std::string& name); static bool jactive(const std::string& name); @@ -51,7 +53,7 @@ public: static std::string writeBindings(); static void loadBindings(const std::string& filename, const std::string& source); - static void loadBindingsToml(const std::string& filename, const std::string& source); + static void loadBindingsOld(const std::string& filename, const std::string& source); // TODO: remove in 0.22 }; #endif // WINDOW_EVENTS_HPP_ diff --git a/src/window/input.cpp b/src/window/input.cpp index 33621f70..cfb06e84 100644 --- a/src/window/input.cpp +++ b/src/window/input.cpp @@ -40,6 +40,25 @@ static std::unordered_map mousecodes { {"middle", GLFW_MOUSE_BUTTON_3}, }; +static std::unordered_map keynames {}; + +std::string input_util::get_name(mousecode code) { + switch (code) { + case mousecode::BUTTON_1: return "left"; + case mousecode::BUTTON_2: return "right"; + case mousecode::BUTTON_3: return "middle"; + default: return "unknown"; + } +} + +std::string input_util::get_name(keycode code) { + auto found = keynames.find(static_cast(code)); + if (found == keynames.end()) { + return "unknown"; + } + return found->second; +} + void Binding::reset(inputtype type, int code) { this->type = type; this->code = code; @@ -58,11 +77,14 @@ void input_util::initialize() { keycodes[std::to_string(i)] = GLFW_KEY_0+i; } for (int i = 0; i < 25; i++) { - keycodes["f"+std::to_string(i)] = GLFW_KEY_F1+i; + keycodes["f"+std::to_string(i+1)] = GLFW_KEY_F1+i; } for (char i = 'a'; i <= 'z'; i++) { keycodes[std::string({i})] = GLFW_KEY_A-'a'+i; } + for (const auto& entry : keycodes) { + keynames[entry.second] = entry.first; + } } keycode input_util::keycode_from(const std::string& name) { diff --git a/src/window/input.hpp b/src/window/input.hpp index 21ca63c1..50e26446 100644 --- a/src/window/input.hpp +++ b/src/window/input.hpp @@ -122,6 +122,11 @@ namespace input_util { std::string to_string(keycode code); /// @return Mouse button label by keycode std::string to_string(mousecode code); + + /// @return Key name by keycode + std::string get_name(keycode code); + /// @return Mouse button name by keycode + std::string get_name(mousecode code); } enum class inputtype { diff --git a/src/window/input.md b/src/window/input.md deleted file mode 100644 index aa1829bd..00000000 --- a/src/window/input.md +++ /dev/null @@ -1,9 +0,0 @@ -## Key Names -- space, backspace, tab, enter, caps-lock, escape -- left-ctrl, left-shift, left-alt, left-super -- right-ctrl, right-shift, right-alt, right-alt -- delete, home, end, insert, page-up, page-down -- left, right, down, up -- a..z -- 0..9 -- f1..f25 diff --git a/src/world/Level.hpp b/src/world/Level.hpp index e64871ab..7b18f06f 100644 --- a/src/world/Level.hpp +++ b/src/world/Level.hpp @@ -58,8 +58,8 @@ public: std::shared_ptr tObj = std::make_shared(args...); std::shared_ptr obj = std::dynamic_pointer_cast(tObj); - objects.push_back(obj); obj->objectUID = objects.size(); + objects.push_back(obj); obj->spawned(); return tObj; } diff --git a/src/world/World.cpp b/src/world/World.cpp index a7bcab92..380e2f48 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -12,7 +12,6 @@ #include "../voxels/Chunk.hpp" #include "../voxels/Chunks.hpp" #include "../voxels/ChunksStorage.hpp" -#include "../window/Camera.hpp" #include "../world/WorldGenerators.hpp" #include @@ -58,11 +57,11 @@ void World::write(Level* level) { for (size_t i = 0; i < chunks->volume; i++) { auto chunk = chunks->chunks[i]; - if (chunk == nullptr || !chunk->isLighted()) + if (chunk == nullptr || !chunk->flags.lighted) continue; - bool lightsUnsaved = !chunk->isLoadedLights() && + bool lightsUnsaved = !chunk->flags.loadedLights && settings.debug.doWriteLights.get(); - if (!chunk->isUnsaved() && !lightsUnsaved) + if (!chunk->flags.unsaved && !lightsUnsaved) continue; regions.put(chunk.get()); } @@ -135,7 +134,6 @@ std::unique_ptr World::load( } } } - (void)world.release(); return level; } @@ -194,19 +192,20 @@ void World::deserialize(dynamic::Map* root) { if (generator == "") { generator = WorldGenerators::getDefaultGeneratorID(); } - auto verobj = root->map("version"); - if (verobj) { + if (auto verobj = root->map("version")) { int major=0, minor=-1; verobj->num("major", major); verobj->num("minor", minor); - std::cout << "world version: " << major << "." << minor << std::endl; + logger.info() << "world version: " << major << "." << minor; } - auto timeobj = root->map("time"); - if (timeobj) { + if (auto timeobj = root->map("time")) { timeobj->num("day-time", daytime); timeobj->num("day-time-speed", daytimeSpeed); timeobj->num("total-time", totalTime); } + if (auto weatherobj = root->map("weather")) { + weatherobj->num("fog", fog); + } nextInventoryId = root->get("next-inventory-id", 2); } @@ -226,6 +225,9 @@ std::unique_ptr World::serialize() const { timeobj.put("day-time-speed", daytimeSpeed); timeobj.put("total-time", totalTime); + auto& weatherobj = root->putMap("weather"); + weatherobj.put("fog", fog); + root->put("next-inventory-id", nextInventoryId); return root; } diff --git a/src/world/World.hpp b/src/world/World.hpp index 7d9e9748..e720473c 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -49,6 +49,9 @@ public: /// @brief total time passed in the world (not depending on daytimeSpeed) double totalTime = 0.0; + /// @brief will be replaced with weather in future + float fog = 0.0f; + World( std::string name, std::string generator,