Compare commits

..

2 Commits

Author SHA1 Message Date
ShiftyX1
f62574f588 update gitignore 2025-12-08 19:06:41 +03:00
Илья Глазунов
a28f630878 macOS issue with segfault fix 2025-12-01 20:39:44 +03:00
67 changed files with 204 additions and 821 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ['https://boosty.to/mihailris']

View File

@ -5,17 +5,9 @@ on:
branches: [ "main", "release-**"]
pull_request:
branches: [ "main", "dev" ]
workflow_call:
inputs:
build_name:
required: true
type: string
description: 'Build name passed as VC_BUILD_NAME define'
jobs:
build-appimage:
# Only run on GitHub, skip on Gitea
if: github.server_url == 'https://github.com'
strategy:
matrix:
@ -44,14 +36,13 @@ jobs:
sudo make install
cd ../..
- name: Configure
run: cmake -S . -B build -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1 -DVOXELENGINE_BUILD_TESTS=ON -DVC_BUILD_NAME="${{ inputs.build_name }}"
run: cmake -S . -B build -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1 -DVOXELENGINE_BUILD_TESTS=ON
- name: Build
run: cmake --build build -t install
- name: Run tests
run: ctest --test-dir build
- name: Run engine tests
timeout-minutes: 3
continue-on-error: true
timeout-minutes: 1
run: |
chmod +x build/VoxelEngine
chmod +x AppDir/usr/bin/vctest
@ -65,4 +56,4 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: AppImage
path: './*.AppImage*'
path: './*.AppImage*'

View File

@ -8,10 +8,8 @@ on:
jobs:
build:
# Only run on GitHub, skip on Gitea
if: github.server_url == 'https://github.com'
name: Build
uses: ./.github/workflows/cmake.yml
with:
build_type: Release
run_tests: true
run_tests: true

View File

@ -19,8 +19,6 @@ on:
jobs:
build:
# Only run on GitHub, skip on Gitea
if: github.server_url == 'https://github.com'
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
@ -75,4 +73,4 @@ jobs:
name: VoxelEngine
path: |
VoxelEngine
res/*
res/*

View File

@ -5,17 +5,9 @@ on:
branches: [ "main", "release-**"]
pull_request:
branches: [ "main", "dev" ]
workflow_call:
inputs:
build_name:
required: true
type: string
description: 'Build name passed as VC_BUILD_NAME define'
jobs:
build-dmg:
# Only run on GitHub, skip on Gitea
if: github.server_url == 'https://github.com'
runs-on: macos-latest
steps:
@ -28,7 +20,7 @@ jobs:
brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm
- name: Configure
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1
- name: Build
run: cmake --build build -t install
@ -43,12 +35,11 @@ jobs:
run: ctest --output-on-failure --test-dir build
- name: Run engine tests
timeout-minutes: 3
continue-on-error: true
timeout-minutes: 1
run: |
chmod +x build/VoxelEngine
chmod +x AppDir/usr/bin/vctest
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build
- name: Create DMG
run: |
mkdir VoxelEngineDmgContent
@ -61,4 +52,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: VoxelEngineMacOs
path: VoxelEngineMacApp.dmg
path: VoxelEngineMacApp.dmg

View File

@ -1,100 +1,43 @@
# adopted from https://github.com/PrismLauncher/PrismLauncher
name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Engine release version'
required: true
default: "0.0.0"
env:
RELEASE_VERSION: ${{ github.event.inputs.version || github.ref_name || 'testrelease' }}
BRANCH_NAME: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || 'main' }}
- "v*"
jobs:
# Gitea job - create draft release on tag push
gitea-draft-release:
if: github.server_url != 'https://github.com'
runs-on: ubuntu-latest
steps:
- name: Create Draft Release on Gitea
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: VoxelCore ${{ github.ref_name }}
draft: true
body: |
## VoxelCore Release ${{ github.ref_name }}
### Downloads
Артефакты будут загружены вручную после сборки на GitHub.
---
**Full Changelog**: https://git.pyserve.org/PulseStudio/VoxelEngine/commits/tag/${{ github.ref_name }}
token: ${{ secrets.GITHUB_TOKEN }}
build_release:
name: Build Release
uses: ./.github/workflows/cmake.yml
with:
build_type: Release
upload_artifacts: true
# GitHub jobs - full build and release
prepare:
# Only run on GitHub, skip on Gitea
if: github.server_url == 'https://github.com'
create_release:
needs: build_release
runs-on: ubuntu-latest
steps:
- run: echo "exists just for outputs"
outputs:
build_name: '${{ env.RELEASE_VERSION }}'
build_linux:
needs: [prepare]
uses: ./.github/workflows/appimage.yml
with:
build_name: '${{ needs.prepare.outputs.build_name }}'
build_macos:
needs: [prepare]
uses: ./.github/workflows/macos.yml
with:
build_name: '${{ needs.prepare.outputs.build_name }}'
build_windows:
needs: [prepare]
uses: ./.github/workflows/windows-clang.yml
with:
build_name: '${{ needs.prepare.outputs.build_name }}'
publish_release:
runs-on: ubuntu-latest
needs: [build_linux, build_macos, build_windows]
steps:
- name: Checkout Release Branch
uses: actions/checkout@v4
with:
ref: ${{ env.BRANCH_NAME }}
- name: Download Build Artifact
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Show Artifacts
run: |
mkdir release
mv ./artifacts/AppImage/VoxelCore-latest-x86_64.AppImage \
./release/voxelcore-${RELEASE_VERSION}_x86-64.AppImage
mv ./artifacts/VoxelEngineMacOs/VoxelEngineMacApp.dmg \
./release/voxelcore-${RELEASE_VERSION}_macos.dmg
(cd ./artifacts/Windows-Build && zip -r ../../release/voxelcore-${RELEASE_VERSION}_win64.zip .)
ls -la ./release
tree ./release
- name: Create Tag
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: VoxelEngine
- name: Pack artifacts
run: |
chmod +x VoxelEngine
zip -r VoxelEngine.zip res VoxelEngine
- name: Grab and store version
run: |
tag_name=$(echo ${{ github.ref }} | grep -oE "v[^/]+$")
echo "VERSION=$tag_name" >> $GITHUB_ENV
TAG_NAME="v${RELEASE_VERSION}"
git tag -a "${TAG_NAME}" -m "Automated release tag ${TAG_NAME}"
git push origin "${TAG_NAME}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release Draft
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ env.RELEASE_VERSION }}
draft: true
files: |
./release/*
generate_release_notes: true
- name: Create release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }}
name: VoxelEngine ${{ env.VERSION }}
draft: true
prerelease: false
files: |
VoxelEngine.zip

View File

@ -4,18 +4,10 @@ on:
push:
branches: [ "main", "release-**"]
pull_request:
branches: [ "main", "dev" ]
workflow_call:
inputs:
build_name:
required: true
type: string
description: 'Build name passed as VC_BUILD_NAME define'
branches: [ "main", "dev" ]
jobs:
build-windows:
# Only run on GitHub, skip on Gitea
if: github.server_url == 'https://github.com'
strategy:
matrix:
@ -55,7 +47,7 @@ jobs:
export VCPKG_ROOT=$(pwd)/vcpkg
mkdir build
cd build
cmake -G "MinGW Makefiles" -DVCPKG_TARGET_TRIPLET=x64-mingw-static -DVC_BUILD_NAME="${{ inputs.build_name }}" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake ..
cmake -G "MinGW Makefiles" -DVCPKG_TARGET_TRIPLET=x64-mingw-static -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake ..
cmake --build . --config Release
- name: Package for Windows
run: |
@ -74,4 +66,4 @@ jobs:
shell: msys2 {0}
working-directory: ${{ github.workspace }}
run: |
packaged/vctest.exe -e packaged/VoxelCore.exe -d dev/tests -u build
packaged/vctest.exe -e packaged/VoxelCore.exe -d dev/tests -u build

View File

@ -8,7 +8,6 @@ on:
jobs:
build-windows:
if: github.server_url == 'https://github.com'
strategy:
matrix:
@ -51,4 +50,4 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: Windows-Build
path: 'packaged/*'
path: 'packaged/*'

View File

@ -10,8 +10,6 @@ execute_process(COMMAND ${CMAKE_COMMAND} --version)
option(VOXELENGINE_BUILD_APPDIR "Pack linux build" OFF)
option(VOXELENGINE_BUILD_TESTS "Build tests" OFF)
add_compile_definitions(VC_BUILD_NAME="${VC_BUILD_NAME}")
# Need for static compilation on Windows with MSVC clang TODO: Make single build
# on Windows to avoid dependence on combinations of platforms and compilers and
# make it independent
@ -42,7 +40,7 @@ target_link_options(VoxelEngine PRIVATE $<$<CXX_COMPILER_ID:GNU>:-no-pie>)
add_custom_command(
TARGET VoxelEngine
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_CURRENT_SOURCE_DIR}/res $<TARGET_FILE_DIR:VoxelEngine>/res)
if(VOXELENGINE_BUILD_TESTS)

View File

@ -1,297 +0,0 @@
debug.log("=== ZIP Filesystem Tests ===")
-- Helper function to create test directory structure
local function setup_test_directory()
debug.log("Setting up test directory structure")
file.mkdirs("config:zip_test/subdir/deep")
file.write("config:zip_test/root_file.txt", "This is a root level file")
file.write("config:zip_test/subdir/file_in_subdir.txt", "File in subdirectory")
file.write("config:zip_test/subdir/deep/deep_file.txt", "File in deep subdirectory")
file.write("config:zip_test/unicode.txt", "Привет мир! Hello World! 你好世界!")
local bytes = {0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD}
file.write_bytes("config:zip_test/binary.bin", bytes)
debug.log("Test directory structure created")
end
-- Helper function to cleanup
local function cleanup()
debug.log("Cleaning up test files")
if file.exists("config:zip_test") then
file.remove_tree("config:zip_test")
end
if file.exists("config:test_archive.zip") then
file.remove("config:test_archive.zip")
end
end
-- Track found bugs
local bugs_found = {}
local function report_bug(name, description)
table.insert(bugs_found, {name = name, description = description})
debug.log("!!! BUG FOUND: " .. name)
debug.log(" " .. description)
end
cleanup()
debug.log("\n[Test 1] Create test directory structure")
setup_test_directory()
assert(file.isdir("config:zip_test"))
assert(file.isfile("config:zip_test/root_file.txt"))
assert(file.isfile("config:zip_test/subdir/file_in_subdir.txt"))
assert(file.isfile("config:zip_test/subdir/deep/deep_file.txt"))
debug.log("\n[Test 2] Create ZIP archive")
file.create_zip("config:zip_test", "config:test_archive.zip")
assert(file.exists("config:test_archive.zip"), "ZIP archive was not created")
assert(file.isfile("config:test_archive.zip"), "ZIP archive is not a file")
local zip_size = file.length("config:test_archive.zip")
assert(zip_size > 0, "ZIP archive is empty")
debug.log("ZIP archive created, size: " .. tostring(zip_size) .. " bytes")
debug.log("\n[Test 3] Mount ZIP archive")
local entry_point = file.mount("config:test_archive.zip")
assert(entry_point ~= nil, "Mount returned nil")
assert(#entry_point > 0, "Mount returned empty entry point")
debug.log("ZIP mounted at entry point: " .. entry_point)
debug.log("\n[Test 4] Check mounted ZIP is read-only")
assert(not file.is_writeable(entry_point .. ":"), "Mounted ZIP should not be writeable")
-- Check root directory consistency (exists vs isdir methods check)
debug.log("\n[Test 5] Check root directory consistency")
local root_path = entry_point .. ":"
local root_isdir = file.isdir(root_path)
local root_exists = file.exists(root_path)
debug.log("file.isdir('" .. root_path .. "') = " .. tostring(root_isdir))
debug.log("file.exists('" .. root_path .. "') = " .. tostring(root_exists))
if root_isdir and not root_exists then
report_bug("ROOT_EXISTS_INCONSISTENCY",
"file.isdir() returns true for ZIP root but file.exists() returns false. " ..
"ZipFileDevice::exists() should handle empty path like ZipFileDevice::isdir() does.")
end
debug.log("\n[Test 6] List root directory")
local root_entries = file.list(root_path)
debug.log("Root entries count: " .. #root_entries)
for i, entry in ipairs(root_entries) do
debug.log(" [" .. i .. "] '" .. entry .. "'")
end
-- Check path format in ZIP archive (leading slash bug)
debug.log("\n[Test 7] Check path format in ZIP archive")
local test_paths = {
{path = entry_point .. ":root_file.txt", desc = "without leading '/'"},
{path = entry_point .. ":/root_file.txt", desc = "with leading '/'"},
{path = entry_point .. ":subdir", desc = "subdir without leading '/'"},
{path = entry_point .. ":/subdir", desc = "subdir with leading '/'"},
}
local path_without_slash_works = false
local path_with_slash_works = false
local working_prefix = ""
for _, test in ipairs(test_paths) do
local exists = file.exists(test.path)
local isfile = file.isfile(test.path)
local isdir = file.isdir(test.path)
debug.log("Path: '" .. test.path .. "' (" .. test.desc .. ")")
debug.log(" exists=" .. tostring(exists) .. ", isfile=" .. tostring(isfile) .. ", isdir=" .. tostring(isdir))
if test.desc == "without leading '/'" and exists then
path_without_slash_works = true
working_prefix = ""
elseif test.desc == "with leading '/'" and exists then
path_with_slash_works = true
working_prefix = "/"
end
end
if not path_without_slash_works and path_with_slash_works then
report_bug("LEADING_SLASH_IN_ZIP_PATHS",
"file.create_zip generates paths with leading '/' (e.g., '/root_file.txt' instead of 'root_file.txt'). " ..
"This breaks file.list() for root directory and requires paths like 'entry:/file.txt' instead of 'entry:file.txt'. " ..
"The bug is in write_zip() where name = entry.pathPart().substr(root.length()) produces '/file.txt' " ..
"because root doesn't include the trailing slash.")
end
-- Set the working path format
local function make_path(relative_path)
if #working_prefix > 0 then
return entry_point .. ":" .. working_prefix .. relative_path
else
return entry_point .. ":" .. relative_path
end
end
debug.log("\n[Test 8] Read text file (with correct path format)")
local root_file_path = make_path("root_file.txt")
debug.log("Trying to read: " .. root_file_path)
if file.exists(root_file_path) then
local content = file.read(root_file_path)
debug.log("Content: '" .. content .. "'")
assert(content == "This is a root level file", "Content mismatch")
else
debug.log("File does not exist with path: " .. root_file_path)
end
debug.log("\n[Test 9] Check subdirectory")
local subdir_path = make_path("subdir")
debug.log("Checking: " .. subdir_path)
local subdir_exists = file.exists(subdir_path)
local subdir_isdir = file.isdir(subdir_path)
debug.log("exists=" .. tostring(subdir_exists) .. ", isdir=" .. tostring(subdir_isdir))
debug.log("\n[Test 10] Check file in subdirectory")
local subdir_file_path = make_path("subdir/file_in_subdir.txt")
debug.log("Checking: " .. subdir_file_path)
local subdir_file_exists = file.exists(subdir_file_path)
debug.log("exists=" .. tostring(subdir_file_exists))
if subdir_file_exists then
local content = file.read(subdir_file_path)
debug.log("Content: '" .. content .. "'")
end
debug.log("\n[Test 11] List subdirectory")
if file.isdir(subdir_path) then
local subdir_entries = file.list(subdir_path)
debug.log("Subdirectory entries count: " .. #subdir_entries)
for i, entry in ipairs(subdir_entries) do
debug.log(" [" .. i .. "] '" .. entry .. "'")
end
for i, entry in ipairs(subdir_entries) do
local expected_prefix = entry_point .. ":"
if string.sub(entry, 1, #expected_prefix) ~= expected_prefix then
report_bug("LIST_MISSING_ENTRY_POINT",
"file.list() returns paths without entry point prefix. " ..
"Expected '" .. expected_prefix .. "...' but got '" .. entry .. "'")
break
end
end
local subdir_path_slash = subdir_path .. "/"
if file.isdir(subdir_path_slash) then
local subdir_entries_slash = file.list(subdir_path_slash)
debug.log("Subdirectory entries (with trailing '/') count: " .. #subdir_entries_slash)
if #subdir_entries ~= #subdir_entries_slash then
report_bug("TRAILING_SLASH_CHANGES_LIST_RESULT",
"file.list() returns different results for paths with and without trailing slash")
end
end
else
debug.log("Subdirectory does not exist, skipping list test")
end
debug.log("\n[Test 12] Deep nested structure")
local deep_dir = make_path("subdir/deep")
local deep_file = make_path("subdir/deep/deep_file.txt")
debug.log("Deep dir: " .. deep_dir .. " exists=" .. tostring(file.exists(deep_dir)))
debug.log("Deep file: " .. deep_file .. " exists=" .. tostring(file.exists(deep_file)))
debug.log("\n[Test 13] Binary file")
local binary_path = make_path("binary.bin")
debug.log("Binary file: " .. binary_path)
if file.exists(binary_path) then
local read_bytes = file.read_bytes(binary_path)
local expected_bytes = {0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD}
debug.log("Read " .. #read_bytes .. " bytes, expected " .. #expected_bytes)
local bytes_match = (#read_bytes == #expected_bytes)
if bytes_match then
for i, b in ipairs(expected_bytes) do
if read_bytes[i] ~= b then
bytes_match = false
break
end
end
end
debug.log("Binary content matches: " .. tostring(bytes_match))
end
debug.log("\n[Test 14] Unicode content")
local unicode_path = make_path("unicode.txt")
if file.exists(unicode_path) then
local content = file.read(unicode_path)
local expected = "Привет мир! Hello World! 你好世界!"
debug.log("Unicode content matches: " .. tostring(content == expected))
end
debug.log("\n[Test 15] file.parent() for ZIP paths")
local test_file = make_path("subdir/file_in_subdir.txt")
local parent = file.parent(test_file)
debug.log("file.parent('" .. test_file .. "') = '" .. parent .. "'")
local expected_parent = entry_point .. ":" .. working_prefix .. "subdir"
if parent ~= expected_parent then
debug.log("Expected: '" .. expected_parent .. "'")
-- This might be a problem with path normalization
end
debug.log("\n[Test 16] Navigate up using file.parent()")
local path = make_path("subdir/deep/deep_file.txt")
debug.log("Starting: " .. path)
local steps = {}
for i = 1, 5 do
path = file.parent(path)
table.insert(steps, path)
debug.log(" Step " .. i .. ": " .. path)
if path == entry_point .. ":" or path == "" then
break
end
end
debug.log("\n[Test 17] Unmount ZIP archive")
file.unmount(entry_point)
debug.log("Unmounted")
local after_unmount_exists = file.exists(root_file_path)
debug.log("After unmount, file.exists('" .. root_file_path .. "') = " .. tostring(after_unmount_exists))
if after_unmount_exists then
report_bug("UNMOUNT_FILES_STILL_ACCESSIBLE",
"Files are still accessible after file.unmount()")
end
-- Cleanup
debug.log("\n[Cleanup]")
cleanup()
-- Summary
debug.log("\n=== Test Summary ===")
if #bugs_found == 0 then
debug.log("No bugs found!")
else
debug.log("Found " .. #bugs_found .. " bug(s):")
for i, bug in ipairs(bugs_found) do
debug.log(" " .. i .. ". " .. bug.name)
debug.log(" " .. bug.description)
end
end
-- Final assertions to ensure critical functionality works
-- (these will fail the test if the workaround path format doesn't work)
debug.log("\n=== Final Assertions ===")
cleanup()
setup_test_directory()
file.create_zip("config:zip_test", "config:test_archive.zip")
local ep = file.mount("config:test_archive.zip")
-- Critical: Files should be readable from mounted ZIP (with workaround)
local test_content = file.read(ep .. ":/root_file.txt")
assert(test_content == "This is a root level file", "CRITICAL: Cannot read files from mounted ZIP")
-- Critical: Subdirectories should be listable
local test_list = file.list(ep .. ":/subdir")
assert(#test_list > 0, "CRITICAL: Cannot list subdirectories in mounted ZIP")
-- Critical: Deep nested files should be accessible
local deep_content = file.read(ep .. ":/subdir/deep/deep_file.txt")
assert(deep_content == "File in deep subdirectory", "CRITICAL: Cannot read deep nested files")
file.unmount(ep)
cleanup()
debug.log("\n=== ZIP Filesystem Tests Completed ===")
debug.log("Note: " .. #bugs_found .. " non-critical bug(s) detected, see summary above.")

View File

@ -159,8 +159,6 @@ Face culling mode:
- **optional** - face culling among blocks of the same rendering group can be disabled via the `graphics.dense-render` setting.
- **disabled** - face culling among blocks of the same rendering group disabled.
In `optional` mode, disabling `graphics.dense-render` will use the `*_opaque` texture variant (if available).
## Physics
### *obstacle*
@ -218,6 +216,10 @@ Item will be chosen on MMB click on the block.
Example: block `door:door_open` is hidden, so you need to specify `picking-item: "door:door.item"` to bind it to not hidden `door:door` block item.
### *script-name*
Used to specify block script name (to reuse one script to multiple blocks). Name must not contain `packid:scripts/` and extension. Just name.
### *ui-layout*
Block UI XML layout name. Default: string block id.
@ -314,16 +316,6 @@ Example: `base:dirt.item`.
To generate loot, the function `block_loot(block_id: int)` in the `base:util` module should be used.
## Other properties
### *script-name*
Used to specify block script name (to reuse one script to multiple blocks). Name must not contain `packid:scripts/` and extension. Just name.
### Tick Interval - *tick-interval*
The interval in ticks (1/20th of a second). A value of 20 results in an on_block_tick call interval of one second.
## Methods
Methods are used to manage the overwriting of properties when extending a block with other packs.

View File

@ -1,9 +1,6 @@
# Documentation
Documentation for 0.31.12.
> [!WARNING]
> Version is in development. Proceed to [Documentation for 0.30.](https://github.com/MihailRis/voxelcore/blob/release-0.30/doc/en/main-page.md)
Documentation for 0.30.
## Sections

View File

@ -48,7 +48,7 @@ Called on random block update (grass growth)
function on_blocks_tick(tps: int)
```
Called tps (20 / tick-interval) times per second. Use 1/tps instead of `time.delta()`.
Called tps (20) times per second. Use 1/tps instead of `time.delta()`.
```lua
function on_block_tick(x, y, z, tps: number)

View File

@ -168,8 +168,6 @@
- **optional** - отсечение граней среди блоков одной группы отрисовки можно отключить через настройку `graphics.dense-render` (Плотный рендер блоков).
- **disabled** - отсечение граней среди блоков одной группы отрисовки отключено.
В режиме `optional` при отключении `graphics.dense-render` будет использоваться `*_opaque` вариант текстуры (при наличии).
## Физика
### Препятствие - *obstacle*
@ -228,6 +226,11 @@
Пример: блок `door:door_open` скрыт (hidden) поэтому указывается `picking-item: "door:door.item"`
### Имя скрипта - *script-name*
Позволяет указать название скрипта блока. Свойство обеспечивает возможность использования одного скрипта для нескольких блоков.
Название указывается без `пак:scripts/` и расширения.
### Имя макета UI - *ui-layout*
Позволяет указать id XML-макета интерфейса блока. По-умолчанию используется строковый id блока.
@ -322,17 +325,6 @@
Для генерации лута следует использовать функцию `block_loot(block_id: int)` в модуле `base:util`.
## Другое
### Имя скрипта - *script-name*
Позволяет указать название скрипта блока. Свойство обеспечивает возможность использования одного скрипта для нескольких блоков.
Название указывается без `пак:scripts/` и расширения.
### Интервал тактов - *tick-interval*
Интервал в тактах мира (1/20 секуды). Значение 20 приводит к интервалу вызова on_block_tick равному одной секунде.
## Методы
Методы используются для управлением перезаписью свойств при расширении блока другими паками.

View File

@ -1,9 +1,6 @@
# Документация
Документация версии 0.31.12.
> [!WARNING]
> Версия находится в разработке. Перейдите к [документации для 0.30.](https://github.com/MihailRis/voxelcore/blob/release-0.30/doc/ru/main-page.md)
Документация версии 0.30.
## Разделы

View File

@ -48,7 +48,7 @@ function on_random_update(x, y, z)
function on_blocks_tick(tps: int)
```
Вызывается tps (20 / tick-interval) раз в секунду. Используйте 1/tps вместо `time.delta()`.
Вызывается tps (20) раз в секунду. Используйте 1/tps вместо `time.delta()`.
```lua
function on_block_tick(x, y, z, tps: number)

View File

@ -1,6 +1,6 @@
{
"id": "base",
"title": "Base",
"version": "0.31.12",
"version": "0.30",
"description": "basic content package"
}

View File

@ -1,6 +1,3 @@
# default project
name = "default"
base_packs = ["base"]
permissions = [
"network"
]

View File

@ -7,7 +7,7 @@ function on_menu_clear()
end
end
local function setup_backround()
function on_menu_setup()
local controller = {}
function controller.resize_menu_bg()
local w, h = unpack(gui.get_viewport())
@ -17,16 +17,11 @@ local function setup_backround()
end
return w, h
end
local bgid = random.uuid()
gui.root.root:add(string.format(
"<image id='%s' src='gui/menubg' size-func='DATA.resize_menu_bg' "..
"z-index='-1' interactive='true'/>", bgid), controller)
menubg = gui.root[bgid]
gui.root.root:add(
"<image id='menubg' src='gui/menubg' size-func='DATA.resize_menu_bg' "..
"z-index='-1' interactive='true'/>", controller)
menubg = gui.root.menubg
controller.resize_menu_bg()
end
function on_menu_setup()
setup_backround()
menu.page = "main"
menu.visible = true
end

View File

@ -182,9 +182,6 @@ local function clean(iterable, checkFun, ...)
end
network.__process_events = function()
if not network.is_available() then
return
end
local CLIENT_CONNECTED = 1
local CONNECTED_TO_SERVER = 2
local DATAGRAM = 3

View File

@ -13,7 +13,6 @@ block.__perform_ticks = function(delta)
goto continue
end
entry.timer = 0.0
entry.pointer = entry.pointer % #entry
local event = entry.event
local tps = entry.tps
for i=1, steps do

View File

@ -46,7 +46,6 @@ local _ffi = ffi
function __vc_Canvas_set_data(self, data)
if type(data) == "cdata" then
self:_set_data(tostring(_ffi.cast("uintptr_t", data.bytes)), data.size)
return
end
local width = self.width
local height = self.height
@ -61,7 +60,7 @@ function __vc_Canvas_set_data(self, data)
for i=0, size - 1 do
canvas_ffi_buffer[i] = data[i + 1]
end
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)), size)
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)), data.size)
end
local ipairs_mt_supported = false

View File

@ -14,15 +14,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
find_package(OpenAL CONFIG REQUIRED)
else()
find_package(OpenAL REQUIRED)
# Create OpenAL::OpenAL alias if not exists
if(NOT TARGET OpenAL::OpenAL AND TARGET OpenAL::AL)
add_library(OpenAL::OpenAL ALIAS OpenAL::AL)
elseif(NOT TARGET OpenAL::OpenAL AND OPENAL_FOUND)
add_library(OpenAL::OpenAL INTERFACE IMPORTED)
set_target_properties(OpenAL::OpenAL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENAL_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${OPENAL_LIBRARY}")
endif()
endif()
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)

View File

@ -5,7 +5,6 @@
#include <utility>
#include "coders/imageio.hpp"
#include "coders/commons.hpp"
#include "constants.hpp"
#include "content/Content.hpp"
#include "content/ContentPack.hpp"
@ -72,26 +71,20 @@ aloader_func AssetsLoader::getLoader(AssetType tag) {
void AssetsLoader::loadNext() {
const aloader_entry& entry = entries.front();
logger.info() << "loading " << entry.filename << " as " << entry.alias;
std::string error {};
try {
aloader_func loader = getLoader(entry.tag);
auto postfunc =
loader(this, paths, entry.filename, entry.alias, entry.config);
postfunc(&assets);
} catch (const parsing_error& err) {
error = err.errorLog();
} catch (const std::runtime_error& err) {
error = err.what();
}
if (!error.empty()) {
logger.error() << error;
auto tag = entry.tag;
auto filename = entry.filename;
entries.pop();
throw assetload::error(tag, std::move(filename), std::move(error));
} catch (std::runtime_error& err) {
logger.error() << err.what();
auto type = entry.tag;
std::string filename = entry.filename;
std::string reason = err.what();
entries.pop();
throw assetload::error(type, std::move(filename), std::move(reason));
}
entries.pop();
}
static void add_layouts(

View File

@ -10,21 +10,20 @@ class ObjParser : BasicParser<char> {
std::vector<glm::vec2> uvs {{0, 0}};
std::vector<glm::vec3> normals {{0, 1, 0}};
// TODO: refactor
void parseFace(Mesh& mesh) {
std::vector<Vertex> vertices;
while (hasNext()) {
auto c = peekInLine();
if (c == '\n') {
break;
} else if (hasNext()) {
} else {
uint indices[3] {};
uint i = 0;
do {
char next = peekInLine();
if (is_digit(next)) {
indices[i] = parseSimpleInt(10);
if (hasNext() && peekInLine() == '/') {
if (peekInLine() == '/') {
pos++;
}
} else if (next == '/') {
@ -32,13 +31,13 @@ class ObjParser : BasicParser<char> {
} else {
break;
}
} while (hasNext() && peekInLine() != '\n' && ++i < 3);
} while (peekInLine() != '\n' && ++i < 3);
vertices.push_back(Vertex {
coords[indices[0]], uvs[indices[1]], normals[indices[2]]});
}
}
if (hasNext() && peekInLine() != '\n') {
if (peekInLine() != '\n' && hasNext()) {
skipLine();
}
if (vertices.size() >= 3) {

View File

@ -164,22 +164,12 @@ static void read_in_memory(png_structp pngPtr, png_bytep dst, png_size_t toread)
reader.offset += toread;
}
void png_error_handler(png_structp pngPtr, png_const_charp errorMessage) {
logger.error() << "libpng error: " << errorMessage;
if (pngPtr) {
longjmp(png_jmpbuf(pngPtr), 1);
}
abort(); // Should not be reached if longjmp works
}
std::unique_ptr<ImageData> png::load_image(const ubyte* bytes, size_t size) {
if (size < 8 || !png_check_sig(bytes, 8)) {
throw std::runtime_error("invalid png signature");
}
png_structp pngPtr = nullptr;
pngPtr = png_create_read_struct(
PNG_LIBPNG_VER_STRING, nullptr, png_error_handler, nullptr
);
pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (pngPtr == nullptr) {
throw std::runtime_error("failed png_create_read_struct");
}
@ -190,11 +180,6 @@ std::unique_ptr<ImageData> png::load_image(const ubyte* bytes, size_t size) {
throw std::runtime_error("failed png_create_info_struct");
}
if (setjmp(png_jmpbuf(pngPtr))) {
png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
throw std::runtime_error("failed to decode png");
}
InMemoryReader reader {bytes, size, 0};
png_set_read_fn(pngPtr, &reader, read_in_memory);

View File

@ -6,7 +6,7 @@
#include <string>
inline constexpr int ENGINE_VERSION_MAJOR = 0;
inline constexpr int ENGINE_VERSION_MINOR = 31;
inline constexpr int ENGINE_VERSION_MINOR = 30;
#ifdef NDEBUG
inline constexpr bool ENGINE_DEBUG_BUILD = false;
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
inline constexpr bool ENGINE_DEBUG_BUILD = true;
#endif // NDEBUG
inline const std::string ENGINE_VERSION_STRING = "0.31.12";
inline const std::string ENGINE_VERSION_STRING = "0.30";
/// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3;

View File

@ -289,7 +289,6 @@ void ContentLoader::loadContent(const dv::value& root) {
item.icon = def.name;
item.placingBlock = def.name;
item.tags = def.tags;
item.scriptFile = def.name + BLOCK_ITEM_SUFFIX + ".lua";
for (uint j = 0; j < 4; j++) {
item.emission[j] = def.emission[j];
@ -493,7 +492,6 @@ void ContentLoader::loadScripts(Content& content) {
load_scripts(content, content.items);
for (const auto& [packid, runtime] : content.getPacks()) {
auto env = runtime->getEnvironment();
const auto& pack = runtime->getInfo();
const auto& folder = pack.folder;
@ -502,10 +500,9 @@ void ContentLoader::loadScripts(Content& content) {
// Load entity components
io::path componentsDir = folder / "scripts/components";
foreach_file(componentsDir, [&pack, env](const io::path& file) {
foreach_file(componentsDir, [&pack](const io::path& file) {
auto name = pack.id + ":" + file.stem();
scripting::load_entity_component(
env,
name,
file,
pack.id + ":scripts/components/" + file.name()

View File

@ -247,7 +247,5 @@ template<> void ContentUnitLoader<Block>::loadUnit(
if (def.hidden && def.pickingItem == def.name + BLOCK_ITEM_SUFFIX) {
def.pickingItem = CORE_EMPTY;
}
if (root.has("script-name") || def.scriptFile.empty()) {
def.scriptFile = pack.id + ":scripts/" + def.scriptName + ".lua";
}
def.scriptFile = pack.id + ":scripts/" + def.scriptName + ".lua";
}

View File

@ -94,17 +94,12 @@ bool ClientConnection::alive() const {
static network::Server& create_tcp_server(
DebuggingServer& dbgServer, Engine& engine, int port
) {
auto network = engine.getNetwork();
if (network == nullptr) {
throw std::runtime_error(
"unable to create tcp server: project has no network permission"
);
}
u64id_t serverId = network->openTcpServer(
auto& network = engine.getNetwork();
u64id_t serverId = network.openTcpServer(
port,
[&network, &dbgServer](u64id_t sid, u64id_t id) {
auto& connection = dynamic_cast<network::ReadableConnection&>(
*network->getConnection(id, true)
*network.getConnection(id, true)
);
connection.setPrivate(true);
logger.info() << "connected client " << id << ": "
@ -113,7 +108,7 @@ static network::Server& create_tcp_server(
dbgServer.setClient(id);
}
);
auto& server = *network->getServer(serverId, true);
auto& server = *network.getServer(serverId, true);
server.setPrivate(true);
auto& tcpServer = dynamic_cast<network::TcpServer&>(server);
@ -307,10 +302,8 @@ void DebuggingServer::sendValue(
}
void DebuggingServer::setClient(u64id_t client) {
auto network = engine.getNetwork();
assert (network != nullptr);
connection =
std::make_unique<ClientConnection>(*network, client);
std::make_unique<ClientConnection>(engine.getNetwork(), client);
connectionEstablished = false;
}

View File

@ -11,15 +11,10 @@ static debug::Logger logger("project");
Project::~Project() = default;
dv::value Project::serialize() const {
auto permissionsList = dv::list();
for (const auto& perm : permissions.permissions) {
permissionsList.add(perm);
}
return dv::object({
{"name", name},
{"title", title},
{"base_packs", dv::to_value(basePacks)},
{"permissions", std::move(permissionsList)}
});
}
@ -27,17 +22,6 @@ void Project::deserialize(const dv::value& src) {
src.at("name").get(name);
src.at("title").get(title);
dv::get(src, "base_packs", basePacks);
if (src.has("permissions")) {
std::vector<std::string> perms;
dv::get(src, "permissions", perms);
permissions.permissions =
std::set<std::string>(perms.begin(), perms.end());
}
logger.info() << "permissions: ";
for (const auto& perm : permissions.permissions) {
logger.info() << " - " << perm;
}
}
void Project::loadProjectClientScript() {
@ -59,7 +43,3 @@ void Project::loadProjectStartScript() {
logger.warning() << "project start script does not exists";
}
}
bool Permissions::has(const std::string& name) const {
return permissions.find(name) != permissions.end();
}

View File

@ -1,6 +1,5 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include <memory>
@ -12,22 +11,12 @@ namespace scripting {
class IClientProjectScript;
}
struct Permissions {
static inline std::string WRITE_TO_USER = "write-to-user";
static inline std::string NETWORK = "network";
std::set<std::string> permissions;
bool has(const std::string& name) const;
};
struct Project : Serializable {
std::string name;
std::string title;
std::vector<std::string> basePacks;
std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> setupCoroutine;
Permissions permissions;
~Project();

View File

@ -134,14 +134,10 @@ void Engine::initialize(CoreParameters coreParameters) {
}
paths = std::make_unique<EnginePaths>(params);
loadProject();
paths->setupProject(*project);
editor = std::make_unique<devtools::Editor>(*this);
cmd = std::make_unique<cmd::CommandsInterpreter>();
if (project->permissions.has(Permissions::NETWORK)) {
network = network::Network::create(settings.network);
}
network = network::Network::create(settings.network);
if (!params.debugServerString.empty()) {
try {
@ -236,9 +232,7 @@ void Engine::run() {
}
void Engine::postUpdate() {
if (network) {
network->update();
}
network->update();
postRunnables.run();
scripting::process_post_runnables();
@ -271,8 +265,6 @@ void Engine::nextFrame(bool waitForRefresh) {
}
void Engine::startPauseLoop() {
assert (network != nullptr);
bool initialCursorLocked = false;
if (!isHeadless()) {
initialCursorLocked = input->isCursorLocked();

View File

@ -3,7 +3,9 @@
#include "CoreParameters.hpp"
#include "PostRunnables.hpp"
#include "Time.hpp"
#include "delegates.hpp"
#include "settings.hpp"
#include "typedefs.hpp"
#include "util/ObjectsKeeper.hpp"
#include <memory>
@ -159,8 +161,8 @@ public:
return *window;
}
network::Network* getNetwork() {
return network.get();
network::Network& getNetwork() {
return *network;
}
cmd::CommandsInterpreter& getCmd() {

View File

@ -6,7 +6,6 @@
#include "io/devices/ZipFileDevice.hpp"
#include "maths/util.hpp"
#include "typedefs.hpp"
#include "devtools/Project.hpp"
#include "util/platform.hpp"
#include "util/random.hpp"
#include "util/stringutil.hpp"
@ -44,10 +43,7 @@ static std::string generate_random_base64() {
EnginePaths::EnginePaths(CoreParameters& params)
: resourcesFolder(params.resFolder),
userFilesFolder(params.userFolder),
projectFolder(params.projectFolder),
initiallyWriteables({
"world", "export", "config"
}) {
projectFolder(params.projectFolder) {
if (!params.scriptFile.empty()) {
scriptFolder = params.scriptFile.parent_path();
io::set_device("script", std::make_shared<io::StdfsDevice>(*scriptFolder));
@ -243,22 +239,6 @@ void EnginePaths::setEntryPoints(std::vector<PathsRoot> entryPoints) {
this->entryPoints = std::move(entryPoints);
}
void EnginePaths::setupProject(const Project& project) {
if (project.permissions.has(Permissions::WRITE_TO_USER)) {
initiallyWriteables.insert("user");
}
}
bool EnginePaths::isWriteable(const std::string& entryPoint) const {
if (entryPoint.length() < 2) {
return false;
}
if (entryPoint.substr(0, 2) == "W.") {
return true;
}
return initiallyWriteables.find(entryPoint) != initiallyWriteables.end();
}
std::tuple<std::string, std::string> EnginePaths::parsePath(std::string_view path) {
size_t separator = path.find(':');
if (separator == std::string::npos) {

View File

@ -9,7 +9,6 @@
#include <string>
#include <vector>
#include <tuple>
#include <set>
struct PathsRoot {
std::string name;
@ -43,8 +42,6 @@ private:
std::vector<PathsRoot> roots;
};
struct Project;
class EnginePaths {
public:
ResPaths resPaths;
@ -68,10 +65,6 @@ public:
void setEntryPoints(std::vector<PathsRoot> entryPoints);
void setupProject(const Project& project);
bool isWriteable(const std::string& entryPoint) const;
std::vector<io::path> scanForWorlds() const;
static std::tuple<std::string, std::string> parsePath(std::string_view view);
@ -88,7 +81,6 @@ private:
std::vector<PathsRoot> entryPoints;
std::unordered_map<std::string, std::string> writeables;
std::vector<std::string> mounted;
std::set<std::string> initiallyWriteables;
void cleanup();
};

View File

@ -36,17 +36,9 @@ WindowControl::Result WindowControl::initialize() {
if (!title.empty()) {
title += " - ";
}
std::string buildName;
#ifdef VC_BUILD_NAME
buildName = VC_BUILD_NAME;
#endif
title += "VoxelCore v";
if (buildName.empty()) {
title += std::to_string(ENGINE_VERSION_MAJOR) + "." +
std::to_string(ENGINE_VERSION_MINOR);
} else {
title += buildName;
}
title += "VoxelCore v" +
std::to_string(ENGINE_VERSION_MAJOR) + "." +
std::to_string(ENGINE_VERSION_MINOR);
if (ENGINE_DEBUG_BUILD) {
title += " [debug]";
}

View File

@ -45,7 +45,7 @@ std::shared_ptr<gui::UINode> UiDocument::get(const std::string& id) const {
if (found == map.end()) {
return nullptr;
}
return found->second.lock();
return found->second;
}
const uidocscript& UiDocument::getScript() const {

View File

@ -19,7 +19,7 @@ struct uidocscript {
bool onclose : 1;
};
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
using UINodesMap = std::unordered_map<std::string, std::shared_ptr<gui::UINode>>;
class UiDocument {
std::string id;

View File

@ -59,7 +59,6 @@ std::shared_ptr<UINode> create_debug_panel(
Player& player,
bool allowDebugCheats
) {
auto network = engine.getNetwork();
auto& gui = engine.getGUI();
auto panel = std::make_shared<Panel>(
gui, glm::vec2(300, 200), glm::vec4(5.0f), 2.0f
@ -88,18 +87,17 @@ std::shared_ptr<UINode> create_debug_panel(
fpsMax = fps;
});
if (network) {
panel->listenInterval(1.0f, [network]() {
size_t totalDownload = network->getTotalDownload();
size_t totalUpload = network->getTotalUpload();
netSpeedString =
L"download: " + std::to_wstring(totalDownload - lastTotalDownload) +
L" B/s upload: " + std::to_wstring(totalUpload - lastTotalUpload) +
L" B/s";
lastTotalDownload = totalDownload;
lastTotalUpload = totalUpload;
});
}
panel->listenInterval(1.0f, [&engine]() {
const auto& network = engine.getNetwork();
size_t totalDownload = network.getTotalDownload();
size_t totalUpload = network.getTotalUpload();
netSpeedString =
L"download: " + std::to_wstring(totalDownload - lastTotalDownload) +
L" B/s upload: " + std::to_wstring(totalUpload - lastTotalUpload) +
L" B/s";
lastTotalDownload = totalDownload;
lastTotalUpload = totalUpload;
});
panel->add(create_label(gui, []() { return L"fps: "+fpsString;}));
@ -118,9 +116,7 @@ std::shared_ptr<UINode> create_debug_panel(
panel->add(create_label(gui, []() {
return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack());
}));
if (network) {
panel->add(create_label(gui, []() { return netSpeedString; }));
}
panel->add(create_label(gui, []() { return netSpeedString; }));
panel->add(create_label(gui, [&engine]() {
auto& settings = engine.getSettings();
bool culling = settings.graphics.frustumCulling.get();

View File

@ -28,13 +28,6 @@ void HandsRenderer::renderHands(
const auto& config = *skeleton.config;
modelBatch.setLightsOffset(camera.position);
config.update(skeleton, glm::mat4(1.0f), glm::vec3(), glm::vec3(1.0f));
config.render(
assets,
modelBatch,
skeleton,
glm::mat3(1.0f),
glm::vec3(),
glm::vec3(1.0f)
);
config.update(skeleton, glm::mat4(1.0f), glm::vec3());
config.render(assets, modelBatch, skeleton, glm::mat3(1.0f), glm::vec3());
}

View File

@ -134,16 +134,16 @@ void Skybox::draw(
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
auto p_shader = assets.get<Shader>("ui3d");
p_shader->use();
p_shader->uniformMatrix("u_projview", camera.getProjView(false));
p_shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
float depthScale = 2e3;
float depthScale = 1e3;
for (auto& sprite : sprites) {
batch3d->texture(assets.get<Texture>(sprite.texture));

View File

@ -79,33 +79,6 @@ void ModelViewer::act(float delta) {
camera.position = center - camera.front * distance;
}
static util::TextureRegion determine_texture_region(
const Assets& assets, const std::string& texture
) {
static std::array<std::string, 6> faces {
"blocks:dbg_north",
"blocks:dbg_south",
"blocks:dbg_top",
"blocks:dbg_bottom",
"blocks:dbg_west",
"blocks:dbg_east",
};
if (texture.length() < 2 || texture.at(0) != '$') {
return util::get_texture_region(assets, texture, "blocks:notfound");
}
unsigned char sideIndex = texture.at(1) - '0';
if (sideIndex < faces.size()) {
return util::get_texture_region(
assets,
faces.at(texture.at(1) - '0'),
"blocks:notfound"
);
}
return util::get_texture_region(assets, "blocks:notfound", "");
}
void ModelViewer::draw(const DrawContext& pctx, const Assets& assets) {
camera.setAspectRatio(size.x / size.y);
camera.updateVectors();
@ -131,10 +104,26 @@ void ModelViewer::draw(const DrawContext& pctx, const Assets& assets) {
ui3dShader.uniformMatrix("u_apply", glm::mat4(1.0f));
ui3dShader.uniformMatrix("u_projview", camera.getProjView());
batch->begin();
for (const auto& mesh : model->meshes) {
auto region = determine_texture_region(assets, mesh.texture);
util::TextureRegion region;
if (!mesh.texture.empty() && mesh.texture[0] == '$') {
// todo: refactor
static std::array<std::string, 6> faces {
"blocks:dbg_north",
"blocks:dbg_south",
"blocks:dbg_top",
"blocks:dbg_bottom",
"blocks:dbg_west",
"blocks:dbg_east",
};
region = util::get_texture_region(
assets,
faces.at(mesh.texture.at(1) - '0'),
"blocks:notfound"
);
} else {
region = util::get_texture_region(assets, mesh.texture, "blocks:notfound");
}
batch->texture(region.texture);
batch->setRegion(region.region);
for (const auto& vertex : mesh.vertices) {

View File

@ -385,18 +385,10 @@ bool UINode::isSubnodeOf(const UINode* node) {
void UINode::getIndices(
const std::shared_ptr<UINode>& node,
std::unordered_map<std::string, std::weak_ptr<UINode>>& map
std::unordered_map<std::string, std::shared_ptr<UINode>>& map
) {
const std::string& id = node->getId();
if (!id.empty()) {
const auto& found = map.find(id);
if (found != map.end()) {
auto prev = found->second.lock();
if (prev && prev->getParent()) {
return;
}
}
map[id] = node;
}
auto container = std::dynamic_pointer_cast<gui::Container>(node);

View File

@ -289,7 +289,7 @@ namespace gui {
/// @brief collect all nodes having id
static void getIndices(
const std::shared_ptr<UINode>& node,
std::unordered_map<std::string, std::weak_ptr<UINode>>& map
std::unordered_map<std::string, std::shared_ptr<UINode>>& map
);
static std::shared_ptr<UINode> find(

View File

@ -2,10 +2,9 @@
#include "util/stringutil.hpp"
ItemDef::ItemDef(const std::string& name)
: name(name),
caption(util::id_to_caption(name)),
scriptName(name.substr(name.find(':') + 1)) {
ItemDef::ItemDef(const std::string& name) : name(name) {
caption = util::id_to_caption(name);
description = "";
}
void ItemDef::cloneTo(ItemDef& dst) {
dst.caption = caption;

View File

@ -37,7 +37,7 @@ struct ItemDef {
std::string caption;
/// @brief Item description will shown in inventory
std::string description = "";
std::string description;
dv::value properties = nullptr;
@ -60,7 +60,7 @@ struct ItemDef {
std::string icon = "blocks:notfound";
std::string placingBlock = "core:air";
std::string scriptName;
std::string scriptName = name.substr(name.find(':') + 1);
std::string modelName = name + ".model";

View File

@ -15,11 +15,7 @@ using namespace scripting;
static int l_start_debug_instance(lua::State* L) {
int port = lua::tointeger(L, 1);
if (port == 0) {
auto network = engine->getNetwork();
if (network == nullptr) {
throw std::runtime_error("project has no network permission");
}
port = network->findFreePort();
port = engine->getNetwork().findFreePort();
if (port == -1) {
throw std::runtime_error("could not find free port");
}

View File

@ -1,7 +1,6 @@
#include "libentity.hpp"
#include "content/Content.hpp"
#include "content/ContentPack.hpp"
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "objects/Entities.hpp"
@ -237,14 +236,7 @@ static int l_reload_component(lua::State* L) {
}
auto filename = name.substr(0, pos + 1) + "scripts/components/" +
name.substr(pos + 1) + ".lua";
auto prefix = name.substr(0, pos);
auto runtime = content->getPackRuntime(prefix);
if (runtime == nullptr) {
throw std::runtime_error("pack '" + prefix + "' content is not loaded");
}
scripting::load_entity_component(
runtime->getEnvironment(), name, filename, filename
);
scripting::load_entity_component(name, filename, filename);
return 0;
}

View File

@ -39,7 +39,18 @@ static int l_read(lua::State* L) {
);
}
static std::set<std::string> writeable_entry_points {
"world", "export", "config"
};
static bool is_writeable(const std::string& entryPoint) {
if (entryPoint.length() < 2) {
return false;
}
if (entryPoint.substr(0, 2) == "W.") {
return true;
}
// todo: do better
auto device = io::get_device(entryPoint);
if (device == nullptr) {
return false;
@ -47,7 +58,7 @@ static bool is_writeable(const std::string& entryPoint) {
if (dynamic_cast<io::MemoryDevice*>(device.get())) {
return true;
}
if (engine->getPaths().isWriteable(entryPoint)) {
if (writeable_entry_points.find(entryPoint) != writeable_entry_points.end()) {
return true;
}
return false;

View File

@ -133,7 +133,7 @@ static int l_post(lua::State* L, network::Network& network) {
auto headers = read_headers(L, 3);
int currentRequestId = request_id++;
network.post(
engine->getNetwork().post(
url,
string,
[currentRequestId](std::vector<char> bytes) {
@ -240,7 +240,7 @@ static int l_recv(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
int length = lua::tointeger(L, 2);
auto connection = network.getConnection(id, false);
auto connection = engine->getNetwork().getConnection(id, false);
if (connection == nullptr || connection->getTransportType() != network::TransportType::TCP) {
return 0;
@ -519,21 +519,11 @@ static int l_pull_events(lua::State* L, network::Network& network) {
return 1;
}
static int l_is_available(lua::State* L) {
return lua::pushboolean(L, engine->getNetwork() != nullptr);
}
template <int(*func)(lua::State*, network::Network&)>
int wrap(lua_State* L) {
int result = 0;
try {
auto network = engine->getNetwork();
if (network == nullptr) {
throw std::runtime_error(
"network subsystem is not available in the project"
);
}
result = func(L, *network);
result = func(L, engine->getNetwork());
}
// transform exception with description into lua_error
catch (std::exception& e) {
@ -553,7 +543,6 @@ const luaL_Reg networklib[] = {
{"get_total_upload", wrap<l_get_total_upload>},
{"get_total_download", wrap<l_get_total_download>},
{"find_free_port", wrap<l_find_free_port>},
{"is_available", lua::wrap<l_is_available>},
{"__pull_events", wrap<l_pull_events>},
{"__open_tcp", wrap<l_open_tcp>},
{"__open_udp", wrap<l_open_udp>},

View File

@ -708,15 +708,12 @@ void scripting::load_content_script(
}
void scripting::load_entity_component(
const scriptenv& env,
const std::string& name,
const io::path& file,
const std::string& fileName
const std::string& name, const io::path& file, const std::string& fileName
) {
auto L = lua::get_main_state();
std::string src = io::read_string(file);
logger.info() << "script (component) " << file.string();
lua::loadbuffer(L, *env, src, fileName);
lua::loadbuffer(L, 0, src, fileName);
lua::store_in(L, lua::CHUNKS_TABLE, name);
}

View File

@ -186,12 +186,10 @@ namespace scripting {
);
/// @brief Load component script
/// @param env environment
/// @param name component full name (packid:name)
/// @param file component script file path
/// @param fileName script file path using the engine format
void load_entity_component(
const scriptenv& env,
const std::string& name,
const io::path& file,
const std::string& fileName

View File

@ -14,10 +14,6 @@ static void sigterm_handler(int signum) {
}
int main(int argc, char** argv) {
#ifdef VC_BUILD_NAME
logger.info() << "build: " << VC_BUILD_NAME;
#endif
CoreParameters coreParameters;
try {
if (!parse_cmdline(argc, argv, coreParameters)) {

View File

@ -194,6 +194,5 @@ void Network::update() {
}
std::unique_ptr<Network> Network::create(const NetworkSettings& settings) {
logger.info() << "initializing network";
return std::make_unique<Network>(network::create_curl_requests());
}

View File

@ -302,7 +302,6 @@ void Entities::updatePhysics(float delta) {
hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f)
? 8.0f
: (!grounded ? 2.0f : 10.0f);
hitbox.scale = transform.size;
transform.setPos(hitbox.position);
if (hitbox.grounded && !grounded) {
scripting::on_entity_grounded(
@ -359,9 +358,7 @@ void Entities::renderDebug(
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
continue;
}
batch.box(
hitbox.position, hitbox.getHalfSize() * 2.0f, glm::vec4(1.0f)
);
batch.box(hitbox.position, hitbox.halfsize * 2.0f, glm::vec4(1.0f));
for (auto& sensor : rigidbody.sensors) {
if (sensor.type != SensorType::AABB) continue;
@ -413,14 +410,10 @@ void Entities::render(
}
const auto& pos = transform.pos;
const auto& size = transform.size;
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
continue;
if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) {
const auto* rigConfig = skeleton.config;
rigConfig->render(assets, batch, skeleton, transform.rot, pos);
}
const auto* rigConfig = skeleton.config;
rigConfig->render(
assets, batch, skeleton, transform.rot, pos, size
);
}
}

View File

@ -23,19 +23,19 @@ struct Transform {
void refresh();
inline void setRot(const glm::mat3& m) {
inline void setRot(glm::mat3 m) {
rot = m;
dirty = true;
}
inline void setSize(const glm::vec3& v) {
inline void setSize(glm::vec3 v) {
if (glm::distance2(displaySize, v) >= EPSILON) {
dirty = true;
}
size = v;
}
inline void setPos(const glm::vec3& v) {
inline void setPos(glm::vec3 v) {
if (glm::distance2(displayPos, v) >= EPSILON) {
dirty = true;
}

View File

@ -122,21 +122,15 @@ size_t SkeletonConfig::update(
return count;
}
static glm::mat4 build_matrix(
const glm::mat3& rot, const glm::vec3& pos, const glm::vec3& scale
) {
static glm::mat4 build_matrix(const glm::mat3& rot, const glm::vec3& pos) {
glm::mat4 combined(1.0f);
combined = glm::translate(combined, pos);
combined = combined * glm::mat4(rot);
combined = glm::scale(combined, scale);
return combined;
}
void SkeletonConfig::update(
Skeleton& skeleton,
const glm::mat3& rotation,
const glm::vec3& position,
const glm::vec3& scale
Skeleton& skeleton, const glm::mat3& rotation, const glm::vec3& position
) const {
if (skeleton.interpolation.isEnabled()) {
const auto& interpolation = skeleton.interpolation;
@ -144,10 +138,10 @@ void SkeletonConfig::update(
0,
skeleton,
root.get(),
build_matrix(rotation, interpolation.getCurrent(), scale)
build_matrix(rotation, interpolation.getCurrent())
);
} else {
update(0, skeleton, root.get(), build_matrix(rotation, position, scale));
update(0, skeleton, root.get(), build_matrix(rotation, position));
}
}
@ -156,10 +150,9 @@ void SkeletonConfig::render(
ModelBatch& batch,
Skeleton& skeleton,
const glm::mat3& rotation,
const glm::vec3& position,
const glm::vec3& scale
const glm::vec3& position
) const {
update(skeleton, rotation, position, scale);
update(skeleton, rotation, position);
if (!skeleton.visible) {
return;

View File

@ -121,8 +121,7 @@ namespace rigging {
void update(
Skeleton& skeleton,
const glm::mat3& rotation,
const glm::vec3& position,
const glm::vec3& scale
const glm::vec3& position
) const;
void render(
@ -130,8 +129,7 @@ namespace rigging {
ModelBatch& batch,
Skeleton& skeleton,
const glm::mat3& rotation,
const glm::vec3& position,
const glm::vec3& scale
const glm::vec3& position
) const;
Skeleton instance() const {

View File

@ -52,7 +52,6 @@ struct Hitbox {
glm::vec3 position;
glm::vec3 halfsize;
glm::vec3 velocity;
glm::vec3 scale {1.0f, 1.0f, 1.0f};
float linearDamping = 0.5;
float friction = 1.0f;
float verticalDamping = 1.0f;
@ -65,8 +64,4 @@ struct Hitbox {
AABB getAABB() const {
return AABB(position-halfsize, position+halfsize);
}
glm::vec3 getHalfSize() const {
return halfsize * scale;
}
};

View File

@ -11,10 +11,11 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
inline const float E = 0.03f;
inline const float MAX_FIX = 0.1f;
const float E = 0.03f;
const float MAX_FIX = 0.1f;
PhysicsSolver::PhysicsSolver(glm::vec3 gravity) : gravity(gravity) {}
PhysicsSolver::PhysicsSolver(glm::vec3 gravity) : gravity(gravity) {
}
void PhysicsSolver::step(
const GlobalChunks& chunks,
@ -27,7 +28,7 @@ void PhysicsSolver::step(
float linearDamping = hitbox.linearDamping * hitbox.friction;
float s = 2.0f/BLOCK_AABB_GRID;
auto half = hitbox.getHalfSize();
const glm::vec3& half = hitbox.halfsize;
glm::vec3& pos = hitbox.position;
glm::vec3& vel = hitbox.velocity;
float gravityScale = hitbox.gravityScale;
@ -90,8 +91,8 @@ void PhysicsSolver::step(
}
AABB aabb;
aabb.a = hitbox.position - hitbox.getHalfSize();
aabb.b = hitbox.position + hitbox.getHalfSize();
aabb.a = hitbox.position - hitbox.halfsize;
aabb.b = hitbox.position + hitbox.halfsize;
for (size_t i = 0; i < sensors.size(); i++) {
auto& sensor = *sensors[i];
if (sensor.entity == entity) {
@ -266,7 +267,7 @@ void PhysicsSolver::colisionCalc(
bool PhysicsSolver::isBlockInside(int x, int y, int z, Hitbox* hitbox) {
const glm::vec3& pos = hitbox->position;
auto half = hitbox->getHalfSize();
const glm::vec3& half = hitbox->halfsize;
return x >= floor(pos.x-half.x) && x <= floor(pos.x+half.x) &&
z >= floor(pos.z-half.z) && z <= floor(pos.z+half.z) &&
y >= floor(pos.y-half.y) && y <= floor(pos.y+half.y);
@ -275,7 +276,7 @@ bool PhysicsSolver::isBlockInside(int x, int y, int z, 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;
auto half = hitbox->getHalfSize();
const glm::vec3& half = hitbox->halfsize;
const auto& boxes = def->rotatable
? def->rt.hitboxes[state.rotation]
: def->hitboxes;

View File

@ -4,6 +4,7 @@
#include <mutex>
#include <vector>
#include <queue>
#include <new>
#if defined(_WIN32)
#include <malloc.h>
@ -35,11 +36,7 @@ namespace util {
std::shared_ptr<T> create(Args&&... args) {
std::lock_guard lock(mutex);
if (freeObjects.empty()) {
try {
allocateNew();
} catch (const std::bad_alloc&) {
return std::make_shared<T>(std::forward<Args>(args)...);
}
allocateNew();
}
auto ptr = freeObjects.front();
freeObjects.pop();
@ -56,6 +53,7 @@ namespace util {
std::mutex mutex;
void allocateNew() {
// Use posix_memalign on POSIX systems as aligned_alloc has stricter requirements
constexpr size_t alignment = alignof(T) < sizeof(void*) ? sizeof(void*) : alignof(T);
constexpr size_t size = sizeof(T);
void* rawPtr = nullptr;

View File

@ -102,7 +102,7 @@ std::string platform::detect_locale() {
if (programLocaleName && preferredLocaleName) {
setlocale(LC_ALL, programLocaleName);
return std::string(preferredLocaleName);
return std::string(preferredLocaleName, 5);
}
return langs::FALLBACK_DEFAULT;
}

View File

@ -58,7 +58,7 @@ static void check_voxels(const ContentIndices& indices, Chunk& chunk) {
abort();
#endif
}
chunk.voxels[i] = {};
chunk.voxels[i].id = BLOCK_AIR;
}
}
}

View File

@ -160,6 +160,12 @@ static void initialize_block(
block_register_events.push_back(BlockRegisterEvent {
static_cast<uint8_t>(bits | 1), def.rt.id, {x, y, z}
});
if (def.rt.funcsset.onblocktick) {
block_register_events.push_back(BlockRegisterEvent {
bits, def.rt.id, {x, y, z}
});
}
}
template <class Storage>

View File

@ -15,7 +15,7 @@ target_link_libraries(VoxelEngineTest PRIVATE VoxelEngineSrc GTest::gtest_main)
add_custom_command(
TARGET VoxelEngineTest
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_SOURCE_DIR}/res ${CMAKE_CURRENT_BINARY_DIR}/res)
include(GoogleTest)

View File

@ -11,19 +11,9 @@ namespace fs = std::filesystem;
TEST(YAML, EncodeDecode) {
io::set_device("root", std::make_shared<io::StdfsDevice>(fs::u8path("../../")));
auto filename = "root:res/project.toml";
auto filename = "root:.github/workflows/windows-clang.yml";
try {
// Test basic YAML parsing with a simple inline string
auto value = yaml::parse(R"(
name: test
version: 1.0
settings:
debug: true
values:
- one
- two
- three
)");
auto value = yaml::parse(io::read_string(filename));
std::cout << yaml::stringify(value) << std::endl;
} catch (const parsing_error& error) {
std::cerr << error.errorLog() << std::endl;