Compare commits
93
Commits
5436b98f3b
...
v0.31.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4c4fb21c0 | ||
|
|
c7f98a176f | ||
|
|
3da03fdd7f | ||
|
|
21874e98c2 | ||
|
|
70aa850999 | ||
|
|
b1837571cc | ||
|
|
51ff1868ac | ||
|
|
5507e17402 | ||
|
|
b68e5567b8 | ||
|
|
42dcf7c888 | ||
|
|
403dbc7791 | ||
|
|
5255fd3a77 | ||
|
|
0107cf13c3 | ||
|
|
ee7b1d4299 | ||
|
|
d3f8942fbf | ||
|
|
5997cbd714 | ||
|
|
06f6550624 | ||
|
|
00f269ca7e | ||
|
|
42248d67eb | ||
|
|
257a293bf4 | ||
|
|
a375af9ca1 | ||
|
|
289347dc91 | ||
|
|
dd40780334 | ||
|
|
56d808e3e2 | ||
|
|
8831d676d4 | ||
|
|
6fca6eab56 | ||
|
|
df1ce1ae53 | ||
|
|
213e9150dd | ||
|
|
43f54c38ec | ||
|
|
f5868b65f0 | ||
|
|
149f418bed | ||
|
|
4893705ff4 | ||
|
|
b36d8d5852 | ||
|
|
17503674e8 | ||
|
|
db09672e50 | ||
|
|
f1d9f1bf60 | ||
|
|
f4adcf58b2 | ||
|
|
2c1d783e0c | ||
|
|
6bd1103f88 | ||
|
|
6becd2f5b5 | ||
|
|
9d05d7c725 | ||
|
|
5971e6b76b | ||
|
|
80852ed368 | ||
|
|
ddf3babac9 | ||
|
|
082b5cd080 | ||
|
|
a15961af8f | ||
|
|
678c42a825 | ||
|
|
85258ff036 | ||
|
|
fa2b933856 | ||
|
|
8ad578e109 | ||
|
|
63cbf12507 | ||
|
|
6f9c3a1389 | ||
|
|
3de1dbed0b | ||
|
|
5f1569a8be | ||
|
|
56cb79f9e2 | ||
|
|
092fe59c81 | ||
|
|
4697969bdb | ||
|
|
be420352eb | ||
|
|
32189b5fa2 | ||
|
|
bc1ff634a3 | ||
|
|
98a3971e13 | ||
|
|
417521e94f | ||
|
|
fbc6d072e8 | ||
|
|
e2e1bd6d76 | ||
|
|
65ee464941 | ||
|
|
9bd6b39952 | ||
|
|
d7f3bf9c7c | ||
|
|
5ae34c2dc2 | ||
|
|
59df377fde | ||
|
|
15b3e4d549 | ||
|
|
b7751cf053 | ||
|
|
a7713d2c44 | ||
|
|
7fc1c37258 | ||
|
|
e257a7f462 | ||
|
|
ecba496f12 | ||
|
|
7c74eece99 | ||
|
|
3a999c6b19 | ||
|
|
a04ea83c48 | ||
|
|
bcfcd9ca76 | ||
|
|
618b3d7f61 | ||
|
|
1f9b10db6e | ||
|
|
59d005c527 | ||
|
|
41a45a961f | ||
|
|
66bb734a4e | ||
|
|
53cf3c17b7 | ||
|
|
c128643cbd | ||
|
|
8110377e7d | ||
|
|
011774a8ba | ||
|
|
933d9b6299 | ||
|
|
64ac93b9d2 | ||
|
|
a70b9f9093 | ||
|
|
7fc9bde6f5 | ||
|
|
3cdf43ecb7 |
@@ -1 +0,0 @@
|
||||
custom: ['https://boosty.to/mihailris']
|
||||
@@ -5,9 +5,17 @@ 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:
|
||||
@@ -36,7 +44,7 @@ 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
|
||||
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 }}"
|
||||
- name: Build
|
||||
run: cmake --build build -t install
|
||||
- name: Run tests
|
||||
@@ -56,4 +64,4 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AppImage
|
||||
path: './*.AppImage*'
|
||||
path: './*.AppImage*'
|
||||
@@ -8,8 +8,10 @@ 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
|
||||
@@ -19,6 +19,8 @@ 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
|
||||
@@ -73,4 +75,4 @@ jobs:
|
||||
name: VoxelEngine
|
||||
path: |
|
||||
VoxelEngine
|
||||
res/*
|
||||
res/*
|
||||
@@ -5,9 +5,17 @@ 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:
|
||||
@@ -20,7 +28,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
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -t install
|
||||
@@ -39,7 +47,7 @@ jobs:
|
||||
run: |
|
||||
chmod +x build/VoxelEngine
|
||||
chmod +x AppDir/usr/bin/vctest
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always
|
||||
- name: Create DMG
|
||||
run: |
|
||||
mkdir VoxelEngineDmgContent
|
||||
@@ -52,4 +60,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: VoxelEngineMacOs
|
||||
path: VoxelEngineMacApp.dmg
|
||||
path: VoxelEngineMacApp.dmg
|
||||
@@ -1,43 +1,100 @@
|
||||
# adopted from https://github.com/PrismLauncher/PrismLauncher
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
- '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' }}
|
||||
jobs:
|
||||
build_release:
|
||||
name: Build Release
|
||||
uses: ./.github/workflows/cmake.yml
|
||||
with:
|
||||
build_type: Release
|
||||
upload_artifacts: true
|
||||
|
||||
create_release:
|
||||
needs: build_release
|
||||
# Gitea job - create draft release on tag push
|
||||
gitea-draft-release:
|
||||
if: github.server_url != 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
- 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 }}
|
||||
|
||||
- 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
|
||||
# GitHub jobs - full build and release
|
||||
prepare:
|
||||
# Only run on GitHub, skip on Gitea
|
||||
if: github.server_url == 'https://github.com'
|
||||
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"
|
||||
|
||||
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
|
||||
|
||||
@@ -4,10 +4,18 @@ on:
|
||||
push:
|
||||
branches: [ "main", "release-**"]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
branches: [ "main", "dev" ]
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_name:
|
||||
required: true
|
||||
type: string
|
||||
description: 'Build name passed as VC_BUILD_NAME define'
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
# Only run on GitHub, skip on Gitea
|
||||
if: github.server_url == 'https://github.com'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -47,7 +55,7 @@ jobs:
|
||||
export VCPKG_ROOT=$(pwd)/vcpkg
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "MinGW Makefiles" -DVCPKG_TARGET_TRIPLET=x64-mingw-static -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake ..
|
||||
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 --build . --config Release
|
||||
- name: Package for Windows
|
||||
run: |
|
||||
@@ -66,4 +74,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
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
if: github.server_url == 'https://github.com'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -50,4 +51,4 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows-Build
|
||||
path: 'packaged/*'
|
||||
path: 'packaged/*'
|
||||
+11
@@ -49,3 +49,14 @@ appimage-build/
|
||||
# libs
|
||||
/libs/
|
||||
/vcpkg_installed/
|
||||
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
CMakeFiles/
|
||||
Makefile
|
||||
compile_commands.json
|
||||
VoxelEngine
|
||||
libVoxelEngineSrc.a
|
||||
vctest
|
||||
|
||||
.vctest/settings.toml
|
||||
|
||||
+121
-110
@@ -1,6 +1,6 @@
|
||||
# 0.29 - 2025.09.20
|
||||
# 0.30 - 2025.11.27
|
||||
|
||||
[Documentation](https://github.com/MihailRis/VoxelEngine-Cpp/tree/release-0.29/doc/en/main-page.md) for 0.29
|
||||
[Documentation](https://github.com/MihailRis/VoxelEngine-Cpp/tree/release-0.30/doc/en/main-page.md) for 0.30
|
||||
|
||||
Table of contents:
|
||||
|
||||
@@ -11,121 +11,132 @@ Table of contents:
|
||||
|
||||
## Added
|
||||
|
||||
- pathfinding
|
||||
- components:
|
||||
- core:pathfinding
|
||||
- core:player
|
||||
- core:mob
|
||||
- audio recording
|
||||
- in-memory filesystems
|
||||
- `:block` placement (generator)
|
||||
- `on_block_present`, `on_block_removed` events
|
||||
- debugging server
|
||||
- `rotate` and `origin` vcm modifiers (rotation)
|
||||
- custom audio streams in lua (audio.PCMStream class)
|
||||
- add I16view, I32view, U16view, I32view lua classes
|
||||
- libraries:
|
||||
- random
|
||||
- gfx.skeletons
|
||||
- (documented) assets
|
||||
- udp support
|
||||
- schedules
|
||||
- events:
|
||||
- on_physics_update (components)
|
||||
- on_block_tick(x, y, z, tps) (blocks)
|
||||
- custom hand controller
|
||||
- http headers
|
||||
- named pipes
|
||||
- optimizations:
|
||||
- speed up block.set
|
||||
- speed up vectors
|
||||
- items description
|
||||
- item properties methods
|
||||
- tab + shift+tab
|
||||
- blocks, items tags
|
||||
- pack dependencies versions
|
||||
- ~~allow to disable autospawn position~~ use player.set_spawnpoint
|
||||
- entity.spawn command
|
||||
- project script
|
||||
- gui.root document
|
||||
- time.schedules.world.common: Schedule
|
||||
- compression
|
||||
- audio.input
|
||||
- editing atlas textures feature
|
||||
- add non_reset_packs argument to app.reset_content
|
||||
- freeze debug panel values on cursor unlock
|
||||
- project:content content source
|
||||
- project start application script
|
||||
- uinode.exists property
|
||||
- uinode events:
|
||||
- `onrightclick`
|
||||
- `onmouseover`
|
||||
- `onmouseout`
|
||||
- frameless window mode
|
||||
- settings:
|
||||
- `display.adaptive-menu-fps` experimental flag
|
||||
- `graphics.soft-lighting`
|
||||
- command-line arguments:
|
||||
- `--tps` command line argument for headless mode
|
||||
- `--dbg-server` command line argument
|
||||
- added error callback argument to network.tcp_connect
|
||||
- go back in menu on Escape pressed
|
||||
- publish `hud.exchange-slot` element
|
||||
- add `istoplevel` argument to input.add_callback
|
||||
- engine pause mode (debugging)
|
||||
- rebuild mip-maps on texture reload
|
||||
- documentation:
|
||||
- documented player.set_camera and related
|
||||
- documented `on_hud_render` event
|
||||
- update app library docs
|
||||
- updated content-packs docs (added full pack structure)
|
||||
|
||||
### Changes
|
||||
|
||||
- app.sleep_until - added 'timeout argument'
|
||||
- network.get / post - added 'data' argument to error callback
|
||||
- autorefresh model preview
|
||||
- move player controls to lua
|
||||
- move hand control to lua
|
||||
- optimization:
|
||||
- fixed major chunks loading performance issue
|
||||
- reduced headless mode chunks memory consumption
|
||||
- vecn functions optimization is not experimental now
|
||||
- pass pack environment to menu page script
|
||||
- canvas element autoresize
|
||||
- disable mouse camera control if non-standard camera used
|
||||
- events without prefix are forbidden now
|
||||
- player.* functions now throw exception in headless mode if player id not specified
|
||||
|
||||
### Functions
|
||||
|
||||
- block.model_name
|
||||
- block.has_tag
|
||||
- item.has_tag
|
||||
- item.description
|
||||
- base64.encode_urlsafe
|
||||
- base64.decode_urlsafe
|
||||
- vec2.rotate
|
||||
- vecn.distance
|
||||
- vecn.mix
|
||||
- rigidbody:get_vdamping
|
||||
- rigidbody:set_vdamping
|
||||
- entity:require_component
|
||||
- network.udp_connect
|
||||
- random.random
|
||||
- random.bytes
|
||||
- random.uuid
|
||||
- Random:random
|
||||
- Random:seed
|
||||
- hud.hand_controller
|
||||
- inventory.get_caption
|
||||
- inventory.set_caption
|
||||
- inventory.get_description
|
||||
- inventory.set_description
|
||||
- pathfinding.create_agent
|
||||
- pathfinding.remove_agent
|
||||
- pathfinding.set_enabled
|
||||
- pathfinding.is_enabled
|
||||
- pathfinding.make_route
|
||||
- pathfinding.make_route_async
|
||||
- pathfinding.pull_route
|
||||
- pathfinding.set_max_visited
|
||||
- pathfinding.avoid_tag
|
||||
- gfx.skeletons.get
|
||||
- Skeleton:index
|
||||
- Skeleton:get_model
|
||||
- Skeleton:set_model
|
||||
- Skeleton:get_matrix
|
||||
- Skeleton:set_matrix
|
||||
- Skeleton:get_texture
|
||||
- Skeleton:set_texture
|
||||
- Skeleton:is_visible
|
||||
- Skeleton:set_visible
|
||||
- Skeleton:get_color
|
||||
- Skeleton:set_color
|
||||
- Schedule:set_timeout(time_ms, callback)
|
||||
- Schedule:set_interval(interval_ms, callback, [optional] repetions): int
|
||||
- Schedule:remove_interval(id)
|
||||
- ScheduleGroup:publish(schedule: Schedule)
|
||||
- app.create_memory_device
|
||||
- app.focus
|
||||
- app.get_content_sources
|
||||
- app.open_url
|
||||
- app.set_content_sources
|
||||
- app.start_debug_instance
|
||||
- assets.to_canvas
|
||||
- audio.get_all_input_devices_names
|
||||
- audio.get_input_info
|
||||
- audio.input.fetch
|
||||
- audio.input.request_open
|
||||
- canvas:add
|
||||
- canvas:encode
|
||||
- canvas:get_data
|
||||
- canvas:mul
|
||||
- canvas:rect
|
||||
- canvas:sub
|
||||
- canvas:unbind_texture
|
||||
- Canvas.decode
|
||||
- compression.decode
|
||||
- compression.encode
|
||||
- debug.get_pack_by_frame
|
||||
- file.create_memory_device
|
||||
- gui.ask
|
||||
- gui.set_syntax_styles
|
||||
- gui.show_message
|
||||
- hud.is_open
|
||||
- input.get_mouse_delta
|
||||
- network.find_free_port
|
||||
- PCMStream:create_sound
|
||||
- PCMStream:feed
|
||||
- PCMStream:share
|
||||
- player.get_all
|
||||
- player.get_all_in_radius
|
||||
- player.get_dir
|
||||
- player.get_interaction_distance
|
||||
- player.get_nearest
|
||||
- player.set_interaction_distance
|
||||
- socket:is_nodelay
|
||||
- socket:recv_async
|
||||
- socket:set_nodelay
|
||||
- string.escape_xml
|
||||
- textbox:indexByPos
|
||||
- textbox:lineY
|
||||
- utf8.escape_xml
|
||||
|
||||
## Fixes
|
||||
|
||||
- fix 3d text position / culling
|
||||
- fix fragment:place rotation (#593)
|
||||
- fix server socket creation in macos
|
||||
- fix: base packs not scanned for app scripts
|
||||
- fix lua::getfield and events registering
|
||||
- fix UIDocument::rebuildIndices
|
||||
- fix input library in headless mode
|
||||
- fix rigidbody:set_gravity_scale
|
||||
- fix extended blocks destruction particles spawn spread, offset
|
||||
- fix shaders recompiling
|
||||
- fix: C++ vecn functions precision loss
|
||||
- fix coroutines errors handling
|
||||
- fix: viewport size on toggle fullscreen
|
||||
- fix: fullscreen monitor refresh rate
|
||||
- fix: content menu panel height
|
||||
- fix generation.create_fragment (#596)
|
||||
- fix bytearray:insert (#594)
|
||||
- fix: script overriding
|
||||
- fix: hud.close after hud.show_overlay bug
|
||||
- fix: 'cannot resume dead coroutine' (#569)
|
||||
- fix: skybox is not visible behind translucent blocks
|
||||
- fix: sampler arrays inbdexed with non-constant / uniform-based expressions are forbidden
|
||||
- fix initial weather intensity
|
||||
- fix drop count (560)
|
||||
- fix BasicParser::parseNumber() out of range (560)
|
||||
- fix rotation interpolation (#557)
|
||||
- [fix: "Bytearray expected, got function"](https://github.com/MihailRis/voxelcore/commit/2d1c69ee7e7248d8e86e00c4a2f5cead490cd278)
|
||||
- [fix zero frames texture animation fatal error](https://github.com/MihailRis/voxelcore/commit/e9222976efa67e2fe541962aabb6485363a12354)
|
||||
- [fix non-local players interpolation and head direction](https://github.com/MihailRis/voxelcore/commit/75ef603df0b1958e0c9eb95c2474dc7f3ec34057)
|
||||
- [fix std::bad_alloc caused by corrupted regions](https://github.com/MihailRis/voxelcore/commit/47cdc0213723c74be3e41b39702b656a2db0448d)
|
||||
- [fix 'align' ui property reading](https://github.com/MihailRis/voxelcore/commit/c8ba5b5dbb7980a6576444fed9f7ea66ae1ed32a)
|
||||
- [fix incorrect textbox horizontal scroll](https://github.com/MihailRis/voxelcore/commit/bc86a3d8da4301ea6ce94c52715bd7cf863b0c37)
|
||||
- [fix: wrong environment used in modules imported by require(...)](https://github.com/MihailRis/voxelcore/commit/5626163f17a252212607dc63bfcd726df44bf290)
|
||||
- [fix: some container attributes not available in panel](https://github.com/MihailRis/voxelcore/commit/8a858beeb421495247a8dfae064672bcf6eb4190)
|
||||
- [fix generation.load_fragment](https://github.com/MihailRis/voxelcore/commit/b4ba2da95524025991f07be87b61ecc015f12656)
|
||||
- [fix: byteutil.unpack 'b' is equivalent of 'B'](https://github.com/MihailRis/voxelcore/commit/2a9507b54e58f852b558d7bc9b2cc88397d37a34)
|
||||
- [fix: missing yaml null literals](https://github.com/MihailRis/voxelcore/commit/5755c616f35cc1c1fc5e94ecbc9c38a5a7f52275)
|
||||
- [fix yaml array parsing](https://github.com/MihailRis/voxelcore/commit/026ae756cf4ad4a4febbef58ce2f007b2a0fc974)
|
||||
- [fix mouse click textbox caret set](https://github.com/MihailRis/voxelcore/commit/a1f0c2c2527b91d3a1d4f47eb2c043ebdef60119)
|
||||
- [fix wrapped textbox selection render](https://github.com/MihailRis/voxelcore/commit/76b54a890c35b6edb6d5018078c865f31e966965)
|
||||
- [fix broken dependencies management](https://github.com/MihailRis/voxelcore/commit/ee6f006b797d1a560baa9e333c99ee0181f548b6)
|
||||
- [fix assets.parse_model with 'xml' format](https://github.com/MihailRis/voxelcore/commit/ec94abccbc4604e5d945d2bb8d1a63561797092b)
|
||||
- [fix correct line generation algorithm and bounds calculation](https://github.com/MihailRis/voxelcore/commit/ed9cf8800aea07c169719a3efddae7020e6be7e9)
|
||||
- [fix enable per-variant custom model caching](https://github.com/MihailRis/voxelcore/commit/2a1d2f9354ee2ab623aca9d0059c0a676395d6d0)
|
||||
- [fix: stream stops and dies on underflow](https://github.com/MihailRis/voxelcore/commit/cf561e78a81810fcb70975c7b785938af37b4b64)
|
||||
- [fix Schedule:set_timeout](https://github.com/MihailRis/voxelcore/commit/1e16ab5464b80d258fd955cf0485ebf596298ab3)
|
||||
- [fix major chunks loading performance issue](https://github.com/MihailRis/voxelcore/commit/957f9f59983790583fb57c9e9a3661631f380153)
|
||||
- [fix: undo/redo also available when textbox is not editable](https://github.com/MihailRis/voxelcore/commit/0df5684adf3117f4018e4b34868f6c41ee7125e3)
|
||||
- [fix player library docs](https://github.com/MihailRis/voxelcore/commit/51f07450d89781ce35bd630e07ea8cf33b912b93)
|
||||
- [fix: root node id overriding](https://github.com/MihailRis/voxelcore/commit/91cb5ab7d8ca5376d87e1ad69b8f2cb05278d73d)
|
||||
- [fix extended block placement across chunk borders](https://github.com/MihailRis/voxelcore/commit/b85c5e367a2adea57ab0c3111e3d3b0cc924322b)
|
||||
- [fix modelviewer fbo creation](https://github.com/MihailRis/voxelcore/commit/be6710bc831f700bd9099860c704b3571d1d90fa)
|
||||
- [fix: panel width differs to size specified in xml](https://github.com/MihailRis/voxelcore/commit/8cc51a107e27e1cb5ae8343fa0151db9c9a66852)
|
||||
|
||||
+3
-1
@@ -10,6 +10,8 @@ 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
|
||||
@@ -40,7 +42,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_if_different
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/res $<TARGET_FILE_DIR:VoxelEngine>/res)
|
||||
|
||||
if(VOXELENGINE_BUILD_TESTS)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Latest release
|
||||
|
||||
- [Download](https://github.com/MihailRis/VoxelCore/releases/latest) | [Скачать](https://github.com/MihailRis/VoxelCore/releases/latest)
|
||||
- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.29/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.29/doc/ru/main-page.md)
|
||||
- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.30/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.30/doc/ru/main-page.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
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.")
|
||||
@@ -159,6 +159,8 @@ 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*
|
||||
@@ -216,10 +218,6 @@ 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.
|
||||
@@ -316,6 +314,16 @@ 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.
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
# Documentation
|
||||
|
||||
Documentation for 0.30.
|
||||
Documentation for 0.31.1.
|
||||
|
||||
> [!WARNING]
|
||||
> Version is in development. Proceed to [Documentation for 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/en/main-page.md)
|
||||
> Version is in development. Proceed to [Documentation for 0.30.](https://github.com/MihailRis/voxelcore/blob/release-0.30/doc/en/main-page.md)
|
||||
|
||||
## Sections
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Called on random block update (grass growth)
|
||||
function on_blocks_tick(tps: int)
|
||||
```
|
||||
|
||||
Called tps (20) times per second. Use 1/tps instead of `time.delta()`.
|
||||
Called tps (20 / tick-interval) times per second. Use 1/tps instead of `time.delta()`.
|
||||
|
||||
```lua
|
||||
function on_block_tick(x, y, z, tps: number)
|
||||
|
||||
@@ -168,6 +168,8 @@
|
||||
- **optional** - отсечение граней среди блоков одной группы отрисовки можно отключить через настройку `graphics.dense-render` (Плотный рендер блоков).
|
||||
- **disabled** - отсечение граней среди блоков одной группы отрисовки отключено.
|
||||
|
||||
В режиме `optional` при отключении `graphics.dense-render` будет использоваться `*_opaque` вариант текстуры (при наличии).
|
||||
|
||||
## Физика
|
||||
|
||||
### Препятствие - *obstacle*
|
||||
@@ -226,11 +228,6 @@
|
||||
|
||||
Пример: блок `door:door_open` скрыт (hidden) поэтому указывается `picking-item: "door:door.item"`
|
||||
|
||||
### Имя скрипта - *script-name*
|
||||
|
||||
Позволяет указать название скрипта блока. Свойство обеспечивает возможность использования одного скрипта для нескольких блоков.
|
||||
Название указывается без `пак:scripts/` и расширения.
|
||||
|
||||
### Имя макета UI - *ui-layout*
|
||||
|
||||
Позволяет указать id XML-макета интерфейса блока. По-умолчанию используется строковый id блока.
|
||||
@@ -325,6 +322,17 @@
|
||||
|
||||
Для генерации лута следует использовать функцию `block_loot(block_id: int)` в модуле `base:util`.
|
||||
|
||||
## Другое
|
||||
|
||||
### Имя скрипта - *script-name*
|
||||
|
||||
Позволяет указать название скрипта блока. Свойство обеспечивает возможность использования одного скрипта для нескольких блоков.
|
||||
Название указывается без `пак:scripts/` и расширения.
|
||||
|
||||
### Интервал тактов - *tick-interval*
|
||||
|
||||
Интервал в тактах мира (1/20 секуды). Значение 20 приводит к интервалу вызова on_block_tick равному одной секунде.
|
||||
|
||||
## Методы
|
||||
|
||||
Методы используются для управлением перезаписью свойств при расширении блока другими паками.
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
# Документация
|
||||
|
||||
Документация версии 0.30.
|
||||
Документация версии 0.31.1.
|
||||
|
||||
> [!WARNING]
|
||||
> Версия находится в разработке. Перейдите к [документации для 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
|
||||
> Версия находится в разработке. Перейдите к [документации для 0.30.](https://github.com/MihailRis/voxelcore/blob/release-0.30/doc/ru/main-page.md)
|
||||
|
||||
## Разделы
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ function on_random_update(x, y, z)
|
||||
function on_blocks_tick(tps: int)
|
||||
```
|
||||
|
||||
Вызывается tps (20) раз в секунду. Используйте 1/tps вместо `time.delta()`.
|
||||
Вызывается tps (20 / tick-interval) раз в секунду. Используйте 1/tps вместо `time.delta()`.
|
||||
|
||||
```lua
|
||||
function on_block_tick(x, y, z, tps: number)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "base",
|
||||
"title": "Base",
|
||||
"version": "0.30",
|
||||
"version": "0.31.1",
|
||||
"description": "basic content package"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# default project
|
||||
name = "default"
|
||||
base_packs = ["base"]
|
||||
permissions = [
|
||||
"network"
|
||||
]
|
||||
|
||||
+10
-5
@@ -7,7 +7,7 @@ function on_menu_clear()
|
||||
end
|
||||
end
|
||||
|
||||
function on_menu_setup()
|
||||
local function setup_backround()
|
||||
local controller = {}
|
||||
function controller.resize_menu_bg()
|
||||
local w, h = unpack(gui.get_viewport())
|
||||
@@ -17,11 +17,16 @@ function on_menu_setup()
|
||||
end
|
||||
return w, h
|
||||
end
|
||||
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
|
||||
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]
|
||||
controller.resize_menu_bg()
|
||||
end
|
||||
|
||||
function on_menu_setup()
|
||||
setup_backround()
|
||||
menu.page = "main"
|
||||
menu.visible = true
|
||||
end
|
||||
|
||||
@@ -182,6 +182,9 @@ 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
|
||||
|
||||
@@ -13,6 +13,7 @@ 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
|
||||
|
||||
@@ -45,7 +45,8 @@ local canvas_ffi_buffer_size = 0
|
||||
local _ffi = ffi
|
||||
function __vc_Canvas_set_data(self, data)
|
||||
if type(data) == "cdata" then
|
||||
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
|
||||
self:_set_data(tostring(_ffi.cast("uintptr_t", data.bytes)), data.size)
|
||||
return
|
||||
end
|
||||
local width = self.width
|
||||
local height = self.height
|
||||
@@ -60,7 +61,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)))
|
||||
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)), size)
|
||||
end
|
||||
|
||||
local ipairs_mt_supported = false
|
||||
|
||||
@@ -14,6 +14,15 @@ 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)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "coders/imageio.hpp"
|
||||
#include "coders/commons.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
@@ -71,20 +72,26 @@ 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);
|
||||
entries.pop();
|
||||
} 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));
|
||||
} 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));
|
||||
}
|
||||
entries.pop();
|
||||
}
|
||||
|
||||
static void add_layouts(
|
||||
|
||||
+5
-4
@@ -10,20 +10,21 @@ 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 {
|
||||
} else if (hasNext()) {
|
||||
uint indices[3] {};
|
||||
uint i = 0;
|
||||
do {
|
||||
char next = peekInLine();
|
||||
if (is_digit(next)) {
|
||||
indices[i] = parseSimpleInt(10);
|
||||
if (peekInLine() == '/') {
|
||||
if (hasNext() && peekInLine() == '/') {
|
||||
pos++;
|
||||
}
|
||||
} else if (next == '/') {
|
||||
@@ -31,13 +32,13 @@ class ObjParser : BasicParser<char> {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (peekInLine() != '\n' && ++i < 3);
|
||||
} while (hasNext() && peekInLine() != '\n' && ++i < 3);
|
||||
|
||||
vertices.push_back(Vertex {
|
||||
coords[indices[0]], uvs[indices[1]], normals[indices[2]]});
|
||||
}
|
||||
}
|
||||
if (peekInLine() != '\n' && hasNext()) {
|
||||
if (hasNext() && peekInLine() != '\n') {
|
||||
skipLine();
|
||||
}
|
||||
if (vertices.size() >= 3) {
|
||||
|
||||
+16
-1
@@ -164,12 +164,22 @@ 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, nullptr, nullptr);
|
||||
pngPtr = png_create_read_struct(
|
||||
PNG_LIBPNG_VER_STRING, nullptr, png_error_handler, nullptr
|
||||
);
|
||||
if (pngPtr == nullptr) {
|
||||
throw std::runtime_error("failed png_create_read_struct");
|
||||
}
|
||||
@@ -180,6 +190,11 @@ 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);
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
inline constexpr int ENGINE_VERSION_MAJOR = 0;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 30;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 31;
|
||||
|
||||
#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.30";
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.31.1";
|
||||
|
||||
/// @brief world regions format version
|
||||
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "core_defs.hpp"
|
||||
|
||||
static void load_configs(Input& input, const io::path& root) {
|
||||
static void load_configs(Input* input, const io::path& root) {
|
||||
auto configFolder = root / "config";
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ static std::vector<io::path> default_content_sources {
|
||||
ContentControl::ContentControl(
|
||||
const Project& project,
|
||||
EnginePaths& paths,
|
||||
Input& input,
|
||||
Input* input,
|
||||
std::function<void()> postContent
|
||||
)
|
||||
: paths(paths),
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
ContentControl(
|
||||
const Project& project,
|
||||
EnginePaths& paths,
|
||||
Input& input,
|
||||
Input* input,
|
||||
std::function<void()> postContent
|
||||
);
|
||||
~ContentControl();
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
const std::vector<io::path>& getContentSources() const;
|
||||
private:
|
||||
EnginePaths& paths;
|
||||
Input& input;
|
||||
Input* input;
|
||||
std::unique_ptr<Content> content;
|
||||
std::function<void()> postContent;
|
||||
std::vector<std::string> basePacks;
|
||||
|
||||
@@ -289,6 +289,7 @@ 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];
|
||||
@@ -492,6 +493,7 @@ 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;
|
||||
|
||||
@@ -500,9 +502,10 @@ void ContentLoader::loadScripts(Content& content) {
|
||||
|
||||
// Load entity components
|
||||
io::path componentsDir = folder / "scripts/components";
|
||||
foreach_file(componentsDir, [&pack](const io::path& file) {
|
||||
foreach_file(componentsDir, [&pack, env](const io::path& file) {
|
||||
auto name = pack.id + ":" + file.stem();
|
||||
scripting::load_entity_component(
|
||||
env,
|
||||
name,
|
||||
file,
|
||||
pack.id + ":scripts/components/" + file.name()
|
||||
|
||||
@@ -247,5 +247,7 @@ template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
if (def.hidden && def.pickingItem == def.name + BLOCK_ITEM_SUFFIX) {
|
||||
def.pickingItem = CORE_EMPTY;
|
||||
}
|
||||
def.scriptFile = pack.id + ":scripts/" + def.scriptName + ".lua";
|
||||
if (root.has("script-name") || def.scriptFile.empty()) {
|
||||
def.scriptFile = pack.id + ":scripts/" + def.scriptName + ".lua";
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@
|
||||
#include "coders/toml.hpp"
|
||||
|
||||
// All in-game definitions (blocks, items, etc..)
|
||||
void corecontent::setup(Input& input, ContentBuilder& builder) {
|
||||
void corecontent::setup(Input* input, ContentBuilder& builder) {
|
||||
{
|
||||
Block& block = builder.blocks.create(CORE_AIR);
|
||||
block.replaceable = true;
|
||||
@@ -28,8 +28,8 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
|
||||
}
|
||||
|
||||
auto bindsFile = "res:bindings.toml";
|
||||
if (io::is_regular_file(bindsFile)) {
|
||||
input.getBindings().read(
|
||||
if (input && io::is_regular_file(bindsFile)) {
|
||||
input->getBindings().read(
|
||||
toml::parse(bindsFile, io::read_string(bindsFile)), BindType::BIND
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -32,5 +32,5 @@ class Input;
|
||||
class ContentBuilder;
|
||||
|
||||
namespace corecontent {
|
||||
void setup(Input& input, ContentBuilder& builder);
|
||||
void setup(Input* input, ContentBuilder& builder);
|
||||
}
|
||||
|
||||
@@ -94,12 +94,17 @@ bool ClientConnection::alive() const {
|
||||
static network::Server& create_tcp_server(
|
||||
DebuggingServer& dbgServer, Engine& engine, int port
|
||||
) {
|
||||
auto& network = engine.getNetwork();
|
||||
u64id_t serverId = network.openTcpServer(
|
||||
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(
|
||||
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 << ": "
|
||||
@@ -108,7 +113,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);
|
||||
@@ -302,8 +307,10 @@ void DebuggingServer::sendValue(
|
||||
}
|
||||
|
||||
void DebuggingServer::setClient(u64id_t client) {
|
||||
auto network = engine.getNetwork();
|
||||
assert (network != nullptr);
|
||||
connection =
|
||||
std::make_unique<ClientConnection>(engine.getNetwork(), client);
|
||||
std::make_unique<ClientConnection>(*network, client);
|
||||
connectionEstablished = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,15 @@ 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)}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +27,17 @@ 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() {
|
||||
@@ -43,3 +59,7 @@ 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();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@@ -11,12 +12,22 @@ 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();
|
||||
|
||||
|
||||
+13
-5
@@ -134,10 +134,14 @@ 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>();
|
||||
network = network::Network::create(settings.network);
|
||||
|
||||
if (project->permissions.has(Permissions::NETWORK)) {
|
||||
network = network::Network::create(settings.network);
|
||||
}
|
||||
|
||||
if (!params.debugServerString.empty()) {
|
||||
try {
|
||||
@@ -163,9 +167,9 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
langs::locale_by_envlocale(platform::detect_locale())
|
||||
);
|
||||
}
|
||||
content = std::make_unique<ContentControl>(*project, *paths, *input, [this]() {
|
||||
onContentLoad();
|
||||
});
|
||||
content = std::make_unique<ContentControl>(
|
||||
*project, *paths, input.get(), [this]() { onContentLoad(); }
|
||||
);
|
||||
scripting::initialize(this);
|
||||
|
||||
if (!isHeadless()) {
|
||||
@@ -232,7 +236,9 @@ void Engine::run() {
|
||||
}
|
||||
|
||||
void Engine::postUpdate() {
|
||||
network->update();
|
||||
if (network) {
|
||||
network->update();
|
||||
}
|
||||
postRunnables.run();
|
||||
scripting::process_post_runnables();
|
||||
|
||||
@@ -265,6 +271,8 @@ void Engine::nextFrame(bool waitForRefresh) {
|
||||
}
|
||||
|
||||
void Engine::startPauseLoop() {
|
||||
assert (network != nullptr);
|
||||
|
||||
bool initialCursorLocked = false;
|
||||
if (!isHeadless()) {
|
||||
initialCursorLocked = input->isCursorLocked();
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "CoreParameters.hpp"
|
||||
#include "PostRunnables.hpp"
|
||||
#include "Time.hpp"
|
||||
#include "delegates.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
|
||||
#include <memory>
|
||||
@@ -161,8 +159,8 @@ public:
|
||||
return *window;
|
||||
}
|
||||
|
||||
network::Network& getNetwork() {
|
||||
return *network;
|
||||
network::Network* getNetwork() {
|
||||
return network.get();
|
||||
}
|
||||
|
||||
cmd::CommandsInterpreter& getCmd() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#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"
|
||||
@@ -43,7 +44,10 @@ static std::string generate_random_base64() {
|
||||
EnginePaths::EnginePaths(CoreParameters& params)
|
||||
: resourcesFolder(params.resFolder),
|
||||
userFilesFolder(params.userFolder),
|
||||
projectFolder(params.projectFolder) {
|
||||
projectFolder(params.projectFolder),
|
||||
initiallyWriteables({
|
||||
"world", "export", "config"
|
||||
}) {
|
||||
if (!params.scriptFile.empty()) {
|
||||
scriptFolder = params.scriptFile.parent_path();
|
||||
io::set_device("script", std::make_shared<io::StdfsDevice>(*scriptFolder));
|
||||
@@ -239,6 +243,22 @@ 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) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <set>
|
||||
|
||||
struct PathsRoot {
|
||||
std::string name;
|
||||
@@ -42,6 +43,8 @@ private:
|
||||
std::vector<PathsRoot> roots;
|
||||
};
|
||||
|
||||
struct Project;
|
||||
|
||||
class EnginePaths {
|
||||
public:
|
||||
ResPaths resPaths;
|
||||
@@ -65,6 +68,10 @@ 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);
|
||||
@@ -81,6 +88,7 @@ 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();
|
||||
};
|
||||
|
||||
@@ -36,9 +36,17 @@ WindowControl::Result WindowControl::initialize() {
|
||||
if (!title.empty()) {
|
||||
title += " - ";
|
||||
}
|
||||
title += "VoxelCore v" +
|
||||
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
||||
std::to_string(ENGINE_VERSION_MINOR);
|
||||
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;
|
||||
}
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
title += " [debug]";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
return found->second.lock();
|
||||
}
|
||||
|
||||
const uidocscript& UiDocument::getScript() const {
|
||||
|
||||
@@ -19,7 +19,7 @@ struct uidocscript {
|
||||
bool onclose : 1;
|
||||
};
|
||||
|
||||
using UINodesMap = std::unordered_map<std::string, std::shared_ptr<gui::UINode>>;
|
||||
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
|
||||
|
||||
class UiDocument {
|
||||
std::string id;
|
||||
|
||||
@@ -59,6 +59,7 @@ 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
|
||||
@@ -87,17 +88,18 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
fpsMax = fps;
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
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->add(create_label(gui, []() { return L"fps: "+fpsString;}));
|
||||
|
||||
@@ -116,7 +118,9 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
panel->add(create_label(gui, []() {
|
||||
return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack());
|
||||
}));
|
||||
panel->add(create_label(gui, []() { return netSpeedString; }));
|
||||
if (network) {
|
||||
panel->add(create_label(gui, []() { return netSpeedString; }));
|
||||
}
|
||||
panel->add(create_label(gui, [&engine]() {
|
||||
auto& settings = engine.getSettings();
|
||||
bool culling = settings.graphics.frustumCulling.get();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <GL/glew.h>
|
||||
#include "Texture.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "gl_util.hpp"
|
||||
|
||||
static debug::Logger logger("gl-framebuffer");
|
||||
|
||||
@@ -20,35 +19,21 @@ Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
|
||||
}
|
||||
|
||||
static std::unique_ptr<Texture> create_texture(int width, int height, int format) {
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr
|
||||
);
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glFramebufferTexture2D(
|
||||
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0
|
||||
);
|
||||
return std::make_unique<Texture>(texture, width, height);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
|
||||
return std::make_unique<Texture>(tex, width, height);
|
||||
}
|
||||
|
||||
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
|
||||
: width(width), height(height)
|
||||
{
|
||||
width = std::max<uint>(1, width);
|
||||
height = std::max<uint>(1, height);
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
|
||||
@@ -60,17 +45,11 @@ Framebuffer::Framebuffer(uint width, uint height, bool alpha)
|
||||
// Setup depth attachment
|
||||
glGenRenderbuffers(1, &depth);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, depth);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
||||
glFramebufferRenderbuffer(
|
||||
GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth
|
||||
);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
auto logLine = logger.error();
|
||||
logLine << "framebuffer is not complete: ";
|
||||
logLine << gl::to_string(status);
|
||||
logLine << " (" << status << ")";
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
logger.error() << "framebuffer is not complete!";
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
@@ -99,7 +78,7 @@ void Framebuffer::resize(uint width, uint height) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, depth);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
texture = create_texture(width, height, format);
|
||||
|
||||
@@ -45,40 +45,4 @@ namespace gl {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// TODO: extend
|
||||
inline const char* to_string(GLenum item) {
|
||||
switch (item) {
|
||||
case GL_INVALID_ENUM:
|
||||
return "invalid enum";
|
||||
case GL_INVALID_VALUE:
|
||||
return "invalid value";
|
||||
case GL_INVALID_OPERATION:
|
||||
return "invalid operation";
|
||||
case GL_STACK_OVERFLOW:
|
||||
return "stack overflow";
|
||||
case GL_STACK_UNDERFLOW:
|
||||
return "stack underflow";
|
||||
case GL_OUT_OF_MEMORY:
|
||||
return "out of memory";
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
return "invalid framebuffer operation";
|
||||
case GL_FRAMEBUFFER_UNDEFINED:
|
||||
return "framebuffer undefined";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
||||
return "framebuffer incomplete attachment";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||
return "framebuffer incomplete missing attachment";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
||||
return "framebuffer incomplete draw buffer";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
||||
return "framebuffer incomplete read buffer";
|
||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||
return "framebuffer unsupported";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
|
||||
return "framebuffer incomplete multisample";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@ void HandsRenderer::renderHands(
|
||||
const auto& config = *skeleton.config;
|
||||
|
||||
modelBatch.setLightsOffset(camera.position);
|
||||
config.update(skeleton, glm::mat4(1.0f), glm::vec3());
|
||||
config.render(assets, modelBatch, skeleton, glm::mat3(1.0f), glm::vec3());
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,16 +134,16 @@ void Skybox::draw(
|
||||
DrawContext ctx = pctx.sub();
|
||||
ctx.setBlendMode(BlendMode::addition);
|
||||
|
||||
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));
|
||||
auto shader = assets.get<Shader>("ui3d");
|
||||
shader->use();
|
||||
shader->uniformMatrix("u_projview", camera.getProjView(false));
|
||||
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 = 1e3;
|
||||
float depthScale = 2e3;
|
||||
for (auto& sprite : sprites) {
|
||||
batch3d->texture(assets.get<Texture>(sprite.texture));
|
||||
|
||||
|
||||
@@ -22,6 +22,6 @@ void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
|
||||
void gui::Canvas::setSize(const glm::vec2& size) {
|
||||
UINode::setSize(size);
|
||||
data->extend(size.x, size.y);
|
||||
data->extend(std::max<int>(1, size.x), std::max<int>(1, size.y));
|
||||
texture->reload(*data);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,33 @@ 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();
|
||||
@@ -104,26 +131,10 @@ 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) {
|
||||
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");
|
||||
}
|
||||
auto region = determine_texture_region(assets, mesh.texture);
|
||||
|
||||
batch->texture(region.texture);
|
||||
batch->setRegion(region.region);
|
||||
for (const auto& vertex : mesh.vertices) {
|
||||
|
||||
@@ -385,10 +385,18 @@ bool UINode::isSubnodeOf(const UINode* node) {
|
||||
|
||||
void UINode::getIndices(
|
||||
const std::shared_ptr<UINode>& node,
|
||||
std::unordered_map<std::string, std::shared_ptr<UINode>>& map
|
||||
std::unordered_map<std::string, std::weak_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);
|
||||
|
||||
@@ -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::shared_ptr<UINode>>& map
|
||||
std::unordered_map<std::string, std::weak_ptr<UINode>>& map
|
||||
);
|
||||
|
||||
static std::shared_ptr<UINode> find(
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
ItemDef::ItemDef(const std::string& name) : name(name) {
|
||||
caption = util::id_to_caption(name);
|
||||
description = "";
|
||||
ItemDef::ItemDef(const std::string& name)
|
||||
: name(name),
|
||||
caption(util::id_to_caption(name)),
|
||||
scriptName(name.substr(name.find(':') + 1)) {
|
||||
}
|
||||
void ItemDef::cloneTo(ItemDef& dst) {
|
||||
dst.caption = caption;
|
||||
|
||||
@@ -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 = name.substr(name.find(':') + 1);
|
||||
std::string scriptName;
|
||||
|
||||
std::string modelName = name + ".model";
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ using namespace scripting;
|
||||
static int l_start_debug_instance(lua::State* L) {
|
||||
int port = lua::tointeger(L, 1);
|
||||
if (port == 0) {
|
||||
port = engine->getNetwork().findFreePort();
|
||||
auto network = engine->getNetwork();
|
||||
if (network == nullptr) {
|
||||
throw std::runtime_error("project has no network permission");
|
||||
}
|
||||
port = network->findFreePort();
|
||||
if (port == -1) {
|
||||
throw std::runtime_error("could not find free port");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "libentity.hpp"
|
||||
|
||||
#include "content/Content.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
@@ -236,7 +237,14 @@ static int l_reload_component(lua::State* L) {
|
||||
}
|
||||
auto filename = name.substr(0, pos + 1) + "scripts/components/" +
|
||||
name.substr(pos + 1) + ".lua";
|
||||
scripting::load_entity_component(name, filename, filename);
|
||||
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
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,18 +39,7 @@ 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;
|
||||
@@ -58,7 +47,7 @@ static bool is_writeable(const std::string& entryPoint) {
|
||||
if (dynamic_cast<io::MemoryDevice*>(device.get())) {
|
||||
return true;
|
||||
}
|
||||
if (writeable_entry_points.find(entryPoint) != writeable_entry_points.end()) {
|
||||
if (engine->getPaths().isWriteable(entryPoint)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -133,7 +133,7 @@ static int l_post(lua::State* L, network::Network& network) {
|
||||
auto headers = read_headers(L, 3);
|
||||
int currentRequestId = request_id++;
|
||||
|
||||
engine->getNetwork().post(
|
||||
network.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 = engine->getNetwork().getConnection(id, false);
|
||||
auto connection = network.getConnection(id, false);
|
||||
|
||||
if (connection == nullptr || connection->getTransportType() != network::TransportType::TCP) {
|
||||
return 0;
|
||||
@@ -519,11 +519,21 @@ 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 {
|
||||
result = func(L, engine->getNetwork());
|
||||
auto network = engine->getNetwork();
|
||||
if (network == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"network subsystem is not available in the project"
|
||||
);
|
||||
}
|
||||
result = func(L, *network);
|
||||
}
|
||||
// transform exception with description into lua_error
|
||||
catch (std::exception& e) {
|
||||
@@ -543,6 +553,7 @@ 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>},
|
||||
|
||||
@@ -220,6 +220,14 @@ static int l_set_data(State* L) {
|
||||
|
||||
if (lua::isstring(L, 2)) {
|
||||
auto ptr = reinterpret_cast<ubyte*>(std::stoull(lua::tostring(L, 2)));
|
||||
int len = lua::touinteger(L, 3);
|
||||
if (len < image.getDataSize()) {
|
||||
throw std::runtime_error(
|
||||
"data size mismatch expected " +
|
||||
std::to_string(image.getDataSize()) + ", got " +
|
||||
std::to_string(len)
|
||||
);
|
||||
}
|
||||
std::memcpy(data, ptr, image.getDataSize());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -708,12 +708,15 @@ void scripting::load_content_script(
|
||||
}
|
||||
|
||||
void scripting::load_entity_component(
|
||||
const std::string& name, const io::path& file, const std::string& fileName
|
||||
const scriptenv& env,
|
||||
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, 0, src, fileName);
|
||||
lua::loadbuffer(L, *env, src, fileName);
|
||||
lua::store_in(L, lua::CHUNKS_TABLE, name);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,10 +186,12 @@ 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
|
||||
|
||||
@@ -14,6 +14,10 @@ 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)) {
|
||||
|
||||
@@ -194,5 +194,6 @@ 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());
|
||||
}
|
||||
|
||||
@@ -302,6 +302,7 @@ 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(
|
||||
@@ -358,7 +359,9 @@ void Entities::renderDebug(
|
||||
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
|
||||
continue;
|
||||
}
|
||||
batch.box(hitbox.position, hitbox.halfsize * 2.0f, glm::vec4(1.0f));
|
||||
batch.box(
|
||||
hitbox.position, hitbox.getHalfSize() * 2.0f, glm::vec4(1.0f)
|
||||
);
|
||||
|
||||
for (auto& sensor : rigidbody.sensors) {
|
||||
if (sensor.type != SensorType::AABB) continue;
|
||||
@@ -410,10 +413,14 @@ void Entities::render(
|
||||
}
|
||||
const auto& pos = transform.pos;
|
||||
const auto& size = transform.size;
|
||||
if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) {
|
||||
const auto* rigConfig = skeleton.config;
|
||||
rigConfig->render(assets, batch, skeleton, transform.rot, pos);
|
||||
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* rigConfig = skeleton.config;
|
||||
rigConfig->render(
|
||||
assets, batch, skeleton, transform.rot, pos, size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,19 +23,19 @@ struct Transform {
|
||||
|
||||
void refresh();
|
||||
|
||||
inline void setRot(glm::mat3 m) {
|
||||
inline void setRot(const glm::mat3& m) {
|
||||
rot = m;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
inline void setSize(glm::vec3 v) {
|
||||
inline void setSize(const glm::vec3& v) {
|
||||
if (glm::distance2(displaySize, v) >= EPSILON) {
|
||||
dirty = true;
|
||||
}
|
||||
size = v;
|
||||
}
|
||||
|
||||
inline void setPos(glm::vec3 v) {
|
||||
inline void setPos(const glm::vec3& v) {
|
||||
if (glm::distance2(displayPos, v) >= EPSILON) {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
+13
-6
@@ -122,15 +122,21 @@ size_t SkeletonConfig::update(
|
||||
return count;
|
||||
}
|
||||
|
||||
static glm::mat4 build_matrix(const glm::mat3& rot, const glm::vec3& pos) {
|
||||
static glm::mat4 build_matrix(
|
||||
const glm::mat3& rot, const glm::vec3& pos, const glm::vec3& scale
|
||||
) {
|
||||
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
|
||||
Skeleton& skeleton,
|
||||
const glm::mat3& rotation,
|
||||
const glm::vec3& position,
|
||||
const glm::vec3& scale
|
||||
) const {
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
const auto& interpolation = skeleton.interpolation;
|
||||
@@ -138,10 +144,10 @@ void SkeletonConfig::update(
|
||||
0,
|
||||
skeleton,
|
||||
root.get(),
|
||||
build_matrix(rotation, interpolation.getCurrent())
|
||||
build_matrix(rotation, interpolation.getCurrent(), scale)
|
||||
);
|
||||
} else {
|
||||
update(0, skeleton, root.get(), build_matrix(rotation, position));
|
||||
update(0, skeleton, root.get(), build_matrix(rotation, position, scale));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,9 +156,10 @@ void SkeletonConfig::render(
|
||||
ModelBatch& batch,
|
||||
Skeleton& skeleton,
|
||||
const glm::mat3& rotation,
|
||||
const glm::vec3& position
|
||||
const glm::vec3& position,
|
||||
const glm::vec3& scale
|
||||
) const {
|
||||
update(skeleton, rotation, position);
|
||||
update(skeleton, rotation, position, scale);
|
||||
|
||||
if (!skeleton.visible) {
|
||||
return;
|
||||
|
||||
@@ -121,7 +121,8 @@ namespace rigging {
|
||||
void update(
|
||||
Skeleton& skeleton,
|
||||
const glm::mat3& rotation,
|
||||
const glm::vec3& position
|
||||
const glm::vec3& position,
|
||||
const glm::vec3& scale
|
||||
) const;
|
||||
|
||||
void render(
|
||||
@@ -129,7 +130,8 @@ namespace rigging {
|
||||
ModelBatch& batch,
|
||||
Skeleton& skeleton,
|
||||
const glm::mat3& rotation,
|
||||
const glm::vec3& position
|
||||
const glm::vec3& position,
|
||||
const glm::vec3& scale
|
||||
) const;
|
||||
|
||||
Skeleton instance() const {
|
||||
|
||||
@@ -52,6 +52,7 @@ 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;
|
||||
@@ -64,4 +65,8 @@ struct Hitbox {
|
||||
AABB getAABB() const {
|
||||
return AABB(position-halfsize, position+halfsize);
|
||||
}
|
||||
|
||||
glm::vec3 getHalfSize() const {
|
||||
return halfsize * scale;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
const float E = 0.03f;
|
||||
const float MAX_FIX = 0.1f;
|
||||
inline const float E = 0.03f;
|
||||
inline 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,
|
||||
@@ -28,7 +27,7 @@ void PhysicsSolver::step(
|
||||
float linearDamping = hitbox.linearDamping * hitbox.friction;
|
||||
float s = 2.0f/BLOCK_AABB_GRID;
|
||||
|
||||
const glm::vec3& half = hitbox.halfsize;
|
||||
auto half = hitbox.getHalfSize();
|
||||
glm::vec3& pos = hitbox.position;
|
||||
glm::vec3& vel = hitbox.velocity;
|
||||
float gravityScale = hitbox.gravityScale;
|
||||
@@ -91,8 +90,8 @@ void PhysicsSolver::step(
|
||||
}
|
||||
|
||||
AABB aabb;
|
||||
aabb.a = hitbox.position - hitbox.halfsize;
|
||||
aabb.b = hitbox.position + hitbox.halfsize;
|
||||
aabb.a = hitbox.position - hitbox.getHalfSize();
|
||||
aabb.b = hitbox.position + hitbox.getHalfSize();
|
||||
for (size_t i = 0; i < sensors.size(); i++) {
|
||||
auto& sensor = *sensors[i];
|
||||
if (sensor.entity == entity) {
|
||||
@@ -267,7 +266,7 @@ void PhysicsSolver::colisionCalc(
|
||||
|
||||
bool PhysicsSolver::isBlockInside(int x, int y, int z, Hitbox* hitbox) {
|
||||
const glm::vec3& pos = hitbox->position;
|
||||
const glm::vec3& half = hitbox->halfsize;
|
||||
auto half = hitbox->getHalfSize();
|
||||
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);
|
||||
@@ -276,7 +275,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;
|
||||
const glm::vec3& half = hitbox->halfsize;
|
||||
auto half = hitbox->getHalfSize();
|
||||
const auto& boxes = def->rotatable
|
||||
? def->rt.hitboxes[state.rotation]
|
||||
: def->hitboxes;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <malloc.h>
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
namespace util {
|
||||
@@ -33,7 +35,11 @@ namespace util {
|
||||
std::shared_ptr<T> create(Args&&... args) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (freeObjects.empty()) {
|
||||
allocateNew();
|
||||
try {
|
||||
allocateNew();
|
||||
} catch (const std::bad_alloc&) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
auto ptr = freeObjects.front();
|
||||
freeObjects.pop();
|
||||
@@ -50,13 +56,20 @@ namespace util {
|
||||
std::mutex mutex;
|
||||
|
||||
void allocateNew() {
|
||||
std::unique_ptr<void, AlignedDeleter> ptr(
|
||||
constexpr size_t alignment = alignof(T) < sizeof(void*) ? sizeof(void*) : alignof(T);
|
||||
constexpr size_t size = sizeof(T);
|
||||
void* rawPtr = nullptr;
|
||||
#if defined(_WIN32)
|
||||
_aligned_malloc(sizeof(T), alignof(T))
|
||||
rawPtr = _aligned_malloc(size, alignment);
|
||||
#else
|
||||
std::aligned_alloc(alignof(T), sizeof(T))
|
||||
if (posix_memalign(&rawPtr, alignment, size) != 0) {
|
||||
rawPtr = nullptr;
|
||||
}
|
||||
#endif
|
||||
);
|
||||
if (rawPtr == nullptr) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
std::unique_ptr<void, AlignedDeleter> ptr(rawPtr);
|
||||
freeObjects.push(ptr.get());
|
||||
objects.push_back(std::move(ptr));
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ std::string platform::detect_locale() {
|
||||
if (programLocaleName && preferredLocaleName) {
|
||||
setlocale(LC_ALL, programLocaleName);
|
||||
|
||||
return std::string(preferredLocaleName, 5);
|
||||
return std::string(preferredLocaleName);
|
||||
}
|
||||
return langs::FALLBACK_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ static void check_voxels(const ContentIndices& indices, Chunk& chunk) {
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
chunk.voxels[i].id = BLOCK_AIR;
|
||||
chunk.voxels[i] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,12 +160,6 @@ 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>
|
||||
|
||||
@@ -91,7 +91,7 @@ static inline void generate_pole(
|
||||
voxel* voxels,
|
||||
int x, int z
|
||||
) {
|
||||
uint y = top;
|
||||
uint y = std::min<uint>(top, CHUNK_H - 1);
|
||||
uint layerExtension = 0;
|
||||
for (const auto& layer : layers.layers) {
|
||||
// skip layer if can't be generated under sea level
|
||||
@@ -106,7 +106,9 @@ static inline void generate_pole(
|
||||
} else {
|
||||
layerHeight += layerExtension;
|
||||
}
|
||||
layerHeight = std::min(static_cast<uint>(layerHeight), y+1);
|
||||
layerHeight = std::min(
|
||||
static_cast<uint>(layerHeight), std::min<uint>(CHUNK_H - 1, y + 1)
|
||||
);
|
||||
|
||||
for (uint i = 0; i < layerHeight; i++, y--) {
|
||||
voxels[vox_index(x, y, z)].id = layer.rt.id;
|
||||
|
||||
+1
-1
@@ -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_if_different
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/res ${CMAKE_CURRENT_BINARY_DIR}/res)
|
||||
|
||||
include(GoogleTest)
|
||||
|
||||
+12
-2
@@ -11,9 +11,19 @@ namespace fs = std::filesystem;
|
||||
|
||||
TEST(YAML, EncodeDecode) {
|
||||
io::set_device("root", std::make_shared<io::StdfsDevice>(fs::u8path("../../")));
|
||||
auto filename = "root:.github/workflows/windows-clang.yml";
|
||||
auto filename = "root:res/project.toml";
|
||||
try {
|
||||
auto value = yaml::parse(io::read_string(filename));
|
||||
// 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
|
||||
)");
|
||||
std::cout << yaml::stringify(value) << std::endl;
|
||||
} catch (const parsing_error& error) {
|
||||
std::cerr << error.errorLog() << std::endl;
|
||||
|
||||
Reference in New Issue
Block a user