commit
7bbd8bab34
23
.github/workflows/appimage.yml
vendored
23
.github/workflows/appimage.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: C/C++ AppImage
|
||||
name: x86-64 AppImage
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -20,24 +20,33 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: install dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev \
|
||||
libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev cmake squashfs-tools
|
||||
libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \
|
||||
libcurl4-openssl-dev libgtest-dev cmake squashfs-tools valgrind
|
||||
# fix luajit paths
|
||||
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
|
||||
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||
# install EnTT
|
||||
git clone https://github.com/skypjack/entt.git
|
||||
cd entt/build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
||||
sudo make install
|
||||
cd ../..
|
||||
- name: configure
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1
|
||||
- name: build
|
||||
- name: Configure
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVOXELENGINE_BUILD_APPDIR=1 -DVOXELENGINE_BUILD_TESTS=ON
|
||||
- name: Build
|
||||
run: cmake --build build -t install
|
||||
- name: Run tests
|
||||
run: ctest --test-dir build
|
||||
- name: Run engine tests
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
chmod +x build/VoxelEngine
|
||||
chmod +x AppDir/usr/bin/vctest
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build
|
||||
- name: Build AppImage
|
||||
uses: AppImageCrafters/build-appimage-action@fe2205a4d6056be47051f7b1b3811106e9814910
|
||||
env:
|
||||
|
||||
6
.github/workflows/macos.yml
vendored
6
.github/workflows/macos.yml
vendored
@ -39,6 +39,12 @@ jobs:
|
||||
- name: Run tests
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
|
||||
- name: Run engine tests
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
chmod +x build/VoxelEngine
|
||||
chmod +x AppDir/usr/bin/vctest
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build
|
||||
- name: Create DMG
|
||||
run: |
|
||||
mkdir VoxelEngineDmgContent
|
||||
|
||||
70
.github/workflows/windows-clang.yml
vendored
Normal file
70
.github/workflows/windows-clang.yml
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
name: Windows Build (CLang)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "release-**"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
compiler: clang
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
- uses: msys2/setup-msys2@v2
|
||||
id: msys2
|
||||
name: Setup MSYS2
|
||||
with:
|
||||
msystem: clang64
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-toolchain
|
||||
mingw-w64-clang-x86_64-cmake
|
||||
mingw-w64-clang-x86_64-make
|
||||
mingw-w64-clang-x86_64-luajit
|
||||
git
|
||||
- name: Set up vcpkg
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
git clone https://github.com/microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
./bootstrap-vcpkg.bat
|
||||
./vcpkg integrate install
|
||||
cd ..
|
||||
- name: Configure project with CMake and vcpkg
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
export VCPKG_DEFAULT_TRIPLET=x64-mingw-static
|
||||
export VCPKG_DEFAULT_HOST_TRIPLET=x64-mingw-static
|
||||
export VCPKG_ROOT=./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 --build . --config Release
|
||||
- name: Package for Windows
|
||||
run: |
|
||||
mkdir packaged
|
||||
mkdir packaged/res
|
||||
cp build/VoxelEngine.exe packaged/
|
||||
cp build/vctest/vctest.exe packaged/
|
||||
cp build/*.dll packaged/
|
||||
cp -r build/res/* packaged/res/
|
||||
mv packaged/VoxelEngine.exe packaged/VoxelCore.exe
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows-Build
|
||||
path: 'packaged/*'
|
||||
- name: Run engine tests
|
||||
shell: msys2 {0}
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
packaged/vctest.exe -e packaged/VoxelCore.exe -d dev/tests -u build
|
||||
36
.github/workflows/windows.yml
vendored
36
.github/workflows/windows.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Windows Build
|
||||
name: MSVC Build
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -21,29 +21,33 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Set up vcpkg
|
||||
- name: Bootstrap vcpkg
|
||||
shell: pwsh
|
||||
run: |
|
||||
git clone https://github.com/microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
.\bootstrap-vcpkg.bat
|
||||
.\vcpkg integrate install
|
||||
cd ..
|
||||
${{ github.workspace }}/vcpkg/bootstrap-vcpkg.bat
|
||||
|
||||
- name: Configure and build project with CMake and vcpkg
|
||||
env:
|
||||
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON -DVOXELENGINE_BUILD_TESTS=ON ..
|
||||
cmake --build . --config Release
|
||||
cmake --preset default-vs-msvc-windows
|
||||
cmake --build --preset default-vs-msvc-windows --config Release
|
||||
- name: Run tests
|
||||
run: ctest --preset default-vs-msvc-windows
|
||||
- name: Run engine tests
|
||||
run: |
|
||||
build/vctest/Release/vctest.exe -e build/Release/VoxelEngine.exe -d dev/tests -u build
|
||||
timeout-minutes: 1
|
||||
- name: Package for Windows
|
||||
run: |
|
||||
mkdir packaged
|
||||
cp -r build/* packaged/
|
||||
cp C:/Windows/System32/msvcp140.dll packaged/Release/msvcp140.dll
|
||||
mv packaged/Release/VoxelEngine.exe packaged/Release/VoxelCore.exe
|
||||
cp -r build/Release/* packaged/
|
||||
cp build/vctest/Release/vctest.exe packaged/
|
||||
cp C:/Windows/System32/msvcp140.dll packaged/msvcp140.dll
|
||||
mv packaged/VoxelEngine.exe packaged/VoxelCore.exe
|
||||
working-directory: ${{ github.workspace }}
|
||||
- name: Run tests
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows-Build
|
||||
path: 'packaged/Release/*'
|
||||
path: 'packaged/*'
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -36,10 +36,6 @@ Debug/voxel_engine
|
||||
AppDir
|
||||
appimage-build/
|
||||
|
||||
# for vcpkg
|
||||
/vcpkg/
|
||||
.gitmodules
|
||||
|
||||
# macOS folder attributes
|
||||
*.DS_Store
|
||||
|
||||
|
||||
@ -1,18 +1,24 @@
|
||||
option(VOXELENGINE_BUILD_WINDOWS_VCPKG ON)
|
||||
if(VOXELENGINE_BUILD_WINDOWS_VCPKG AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake")
|
||||
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "")
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
project(VoxelEngine)
|
||||
|
||||
option(VOXELENGINE_BUILD_APPDIR OFF)
|
||||
option(VOXELENGINE_BUILD_TESTS OFF)
|
||||
option(VOXELENGINE_BUILD_APPDIR "" OFF)
|
||||
option(VOXELENGINE_BUILD_TESTS "" OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
# We use two types linking: for clang build is static (vcpkg triplet x64-windows-static)
|
||||
# and for msvc build is dynamic linking (vcpkg triplet x64-windows)
|
||||
# By default CMAKE_MSVC_RUNTIME_LIBRARY set by MultiThreaded$<$<CONFIG:Debug>:Debug>DLL
|
||||
if (VCPKG_TARGET_TRIPLET MATCHES "static")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
add_executable(${PROJECT_NAME} src/voxel_engine.cpp)
|
||||
add_executable(${PROJECT_NAME} src/main.cpp)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
if(VOXELENGINE_BUILD_APPDIR)
|
||||
@ -24,7 +30,6 @@ if(MSVC)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
||||
endif()
|
||||
if((CMAKE_BUILD_TYPE EQUAL "Release") OR (CMAKE_BUILD_TYPE EQUAL "RelWithDebInfo"))
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Release>:Release>")
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /MT /O2)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
|
||||
@ -39,31 +44,9 @@ else()
|
||||
if (CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Og)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(VOXELENGINE_BUILD_WINDOWS_VCPKG AND WIN32)
|
||||
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/.git")
|
||||
find_package(Git QUIET)
|
||||
if(GIT_FOUND)
|
||||
message(STATUS "Adding vcpkg as a git submodule...")
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/microsoft/vcpkg.git vcpkg WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "Git not found, cannot add vcpkg submodule.")
|
||||
endif()
|
||||
if (WIN32)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/.git")
|
||||
message(STATUS "Initializing and updating vcpkg submodule...")
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E chdir vcpkg ./bootstrap-vcpkg.bat WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES})
|
||||
string(TOUPPER ${CONFIG_TYPE} CONFIG_TYPE_UPPER)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/res ${CMAKE_BINARY_DIR}/${CONFIG_TYPE_UPPER}/res)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
@ -76,9 +59,18 @@ endif()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} VoxelEngineSrc ${CMAKE_DL_LIBS})
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
# Deploy res to build dir
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/res
|
||||
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res
|
||||
)
|
||||
|
||||
if (VOXELENGINE_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(vctest)
|
||||
|
||||
35
CMakePresets.json
Normal file
35
CMakePresets.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": 6,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "default-vs-msvc-windows",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"rhs": "${hostSystemName}",
|
||||
"lhs": "Windows"
|
||||
},
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VOXELENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "default-vs-msvc-windows",
|
||||
"configurePreset": "default-vs-msvc-windows",
|
||||
"configuration": "Debug"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "default-vs-msvc-windows",
|
||||
"configurePreset": "default-vs-msvc-windows",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
38
README.md
38
README.md
@ -108,28 +108,32 @@ cmake --build .
|
||||
>[!NOTE]
|
||||
> Requirement:
|
||||
>
|
||||
> vcpkg, CMake
|
||||
> vcpkg, CMake, Git
|
||||
There are two options to use vcpkg:
|
||||
1. If you have Visual Studio installed, most likely the **VCPKG_ROOT** environment variable will already exist in **Developer Command Prompt for VS**
|
||||
2. If you want use **vcpkg**, install **vcpkg** from git to you system:
|
||||
```PowerShell
|
||||
cd C:/
|
||||
git clone https://github.com/microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
.\bootstrap-vcpkg.bat
|
||||
```
|
||||
After installing **vcpkg**, setup env variable **VCPKG_ROOT** and add it to **PATH**:
|
||||
```PowerShell
|
||||
$env:VCPKG_ROOT = "C:\path\to\vcpkg"
|
||||
$env:PATH = "$env:VCPKG_ROOT;$env:PATH"
|
||||
```
|
||||
>[!TIP]
|
||||
>For troubleshooting you can read full [documentation](https://learn.microsoft.com/ru-ru/vcpkg/get_started/get-started?pivots=shell-powershell) for **vcpkg**
|
||||
|
||||
```sh
|
||||
After installing **vcpkg** you can build project:
|
||||
```PowerShell
|
||||
git clone --recursive https://github.com/MihailRis/VoxelEngine-Cpp.git
|
||||
cd VoxelEngine-Cpp
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON ..
|
||||
del CMakeCache.txt
|
||||
rmdir /s /q CMakeFiles
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON ..
|
||||
cmake --build . --config Release
|
||||
cmake --preset default-vs-msvc-windows
|
||||
cmake --build --preset default-vs-msvc-windows
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> You can use ```rm CMakeCache.txt``` and ```rm -rf CMakeFiles``` while using Git Bash
|
||||
|
||||
> [!WARNING]
|
||||
> If you have issues during the vcpkg integration, try navigate to ```vcpkg\downloads```
|
||||
> and extract PowerShell-[version]-win-x86 to ```vcpkg\downloads\tools``` as powershell-core-[version]-windows.
|
||||
> Then rerun ```cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON ..```
|
||||
|
||||
## Build using Docker
|
||||
|
||||
### Step 0. Install docker on your system
|
||||
|
||||
24
dev/tests/base_entities.lua
Normal file
24
dev/tests/base_entities.lua
Normal file
@ -0,0 +1,24 @@
|
||||
local util = require("core:tests_util")
|
||||
|
||||
-- Create world and prepare settings
|
||||
util.create_demo_world("core:default")
|
||||
app.set_setting("chunks.load-distance", 3)
|
||||
app.set_setting("chunks.load-speed", 1)
|
||||
|
||||
-- Create player
|
||||
local pid = player.create("Xerxes")
|
||||
player.set_spawnpoint(pid, 0, 100, 0)
|
||||
player.set_pos(pid, 0, 100, 0)
|
||||
|
||||
-- Wait for chunk to load
|
||||
app.sleep_until(function () return block.get(0, 0, 0) ~= -1 end)
|
||||
|
||||
-- Place a falling block
|
||||
block.place(0, 2, 0, block.index("base:sand"), 0, pid)
|
||||
app.tick()
|
||||
|
||||
-- Check if the block is falling
|
||||
assert(block.get(0, 2, 0) == 0)
|
||||
|
||||
-- Wait for the block to fall
|
||||
app.sleep_until(function () return block.get(0, 1, 0) == block.index("base:sand") end, 100)
|
||||
30
dev/tests/chunks.lua
Normal file
30
dev/tests/chunks.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local util = require "core:tests_util"
|
||||
util.create_demo_world()
|
||||
|
||||
app.set_setting("chunks.load-distance", 3)
|
||||
app.set_setting("chunks.load-speed", 1)
|
||||
|
||||
local pid1 = player.create("Xerxes")
|
||||
assert(player.get_name(pid1) == "Xerxes")
|
||||
|
||||
local pid2 = player.create("Segfault")
|
||||
assert(player.get_name(pid2) == "Segfault")
|
||||
|
||||
local seed = math.floor(math.random() * 1e6)
|
||||
print("random seed", seed)
|
||||
math.randomseed(seed)
|
||||
|
||||
for i=1,25 do
|
||||
if i % 5 == 0 then
|
||||
print(tostring(i*4).." % done")
|
||||
print("chunks loaded", world.count_chunks())
|
||||
end
|
||||
player.set_pos(pid1, math.random() * 100 - 50, 100, math.random() * 100 - 50)
|
||||
player.set_pos(pid2, math.random() * 200 - 100, 100, math.random() * 200 - 100)
|
||||
app.tick()
|
||||
end
|
||||
|
||||
player.delete(pid2)
|
||||
|
||||
app.close_world(true)
|
||||
app.delete_world("demo")
|
||||
50
dev/tests/filesystem.lua
Normal file
50
dev/tests/filesystem.lua
Normal file
@ -0,0 +1,50 @@
|
||||
debug.log("check initial state")
|
||||
assert(file.exists("config:"))
|
||||
|
||||
debug.log("write text file")
|
||||
assert(file.write("config:text.txt", "example, пример"))
|
||||
assert(file.exists("config:text.txt"))
|
||||
|
||||
debug.log("read text file")
|
||||
assert(file.read("config:text.txt") == "example, пример")
|
||||
|
||||
debug.log("delete file")
|
||||
file.remove("config:text.txt")
|
||||
assert(not file.exists("config:text.txt"))
|
||||
|
||||
debug.log("create directory")
|
||||
file.mkdir("config:dir")
|
||||
assert(file.isdir("config:dir"))
|
||||
|
||||
debug.log("remove directory")
|
||||
file.remove("config:dir")
|
||||
|
||||
debug.log("create directories")
|
||||
file.mkdirs("config:dir/subdir/other")
|
||||
assert(file.isdir("config:dir/subdir/other"))
|
||||
|
||||
debug.log("remove tree")
|
||||
file.remove_tree("config:dir")
|
||||
assert(not file.isdir("config:dir"))
|
||||
|
||||
debug.log("write binary file")
|
||||
local bytes = {0xDE, 0xAD, 0xC0, 0xDE}
|
||||
file.write_bytes("config:binary", bytes)
|
||||
assert(file.exists("config:binary"))
|
||||
|
||||
debug.log("read binary file")
|
||||
local rbytes = file.read_bytes("config:binary")
|
||||
assert(#rbytes == #bytes)
|
||||
for i, b in ipairs(bytes) do
|
||||
assert(rbytes[i] == b)
|
||||
end
|
||||
|
||||
debug.log("delete file")
|
||||
file.remove("config:binary")
|
||||
assert(not file.exists("config:binary"))
|
||||
|
||||
debug.log("checking entry points for writeability")
|
||||
assert(file.is_writeable("config:"))
|
||||
assert(file.is_writeable("export:"))
|
||||
assert(not file.is_writeable("user:"))
|
||||
assert(not file.is_writeable("res:"))
|
||||
30
dev/tests/world.lua
Normal file
30
dev/tests/world.lua
Normal file
@ -0,0 +1,30 @@
|
||||
-- Create/close/open/close world
|
||||
|
||||
-- Open
|
||||
app.reconfig_packs({"base"}, {})
|
||||
app.new_world("demo", "2019", "core:default")
|
||||
assert(world.is_open())
|
||||
assert(world.get_generator() == "core:default")
|
||||
app.sleep(1)
|
||||
assert(world.get_total_time() > 0.0)
|
||||
print(world.get_total_time())
|
||||
|
||||
-- Close
|
||||
app.close_world(true)
|
||||
assert(not world.is_open())
|
||||
|
||||
-- Reopen
|
||||
app.open_world("demo")
|
||||
assert(world.is_open())
|
||||
assert(world.get_total_time() > 0.0)
|
||||
assert(world.get_seed() == 2019)
|
||||
app.tick()
|
||||
|
||||
-- Remove base pack
|
||||
app.reconfig_packs({}, {"base"})
|
||||
-- World is reopened in post-runnable
|
||||
app.tick()
|
||||
|
||||
-- Close
|
||||
app.close_world(true)
|
||||
app.delete_world("demo")
|
||||
46
dev/valgrind.suppress
Normal file
46
dev/valgrind.suppress
Normal file
@ -0,0 +1,46 @@
|
||||
# Valgrind suppression file for VoxelCore
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
fun:lua_getfield
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
fun:lua_setfield
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
fun:lua_loadx
|
||||
fun:luaL_loadbufferx
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
fun:lua_pushstring
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libluajit-5.1.so.2.1.0
|
||||
fun:lua_pushlstring
|
||||
}
|
||||
{
|
||||
glewInit internal leak
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
obj:*
|
||||
obj:*
|
||||
fun:glXGetClientString
|
||||
fun:glxewInit
|
||||
fun:glewInit
|
||||
}
|
||||
@ -213,3 +213,39 @@ User properties must be declared in `pack:config/user-props.toml` file:
|
||||
```
|
||||
|
||||
Example: [user properties of pack **base**](../../res/content/base/config/user-props.toml).
|
||||
|
||||
## Properties introduced by the `base` pack
|
||||
|
||||
### *base:durability*
|
||||
|
||||
The time it takes to break a block without tools or effects, measured in seconds.
|
||||
|
||||
### Loot - *base:loot*
|
||||
|
||||
A list of tables with properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"item": "pack:item",
|
||||
"min": 1,
|
||||
"max": 3,
|
||||
"chance": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
- `count` defaults to 1. It does not need to be specified if `min` and `max` are provided.
|
||||
- `min`, `max` - the minimum and maximum quantity of the item.
|
||||
- `chance` - the probability of the item dropping. Defaults to 1.0.
|
||||
|
||||
It should be noted that the `item` refers specifically to the item. That is, to specify the item of a block, you need to add `.item` after the block name.
|
||||
Example: `base:dirt.item`.
|
||||
|
||||
To generate loot, the function `block_loot(block_id: int)` in the `base:util` module should be used.
|
||||
|
||||
## Methods
|
||||
|
||||
Methods are used to manage the overwriting of properties when extending a block with other packs.
|
||||
|
||||
### `property_name@append`
|
||||
|
||||
Adds elements to the end of the list instead of completely overwriting it.
|
||||
|
||||
@ -9,9 +9,11 @@ Subsections:
|
||||
- [UI properties and methods](scripting/ui.md)
|
||||
- [Entities and components](scripting/ecs.md)
|
||||
- [Libraries](#)
|
||||
- [app](scripting/builtins/libapp.md)
|
||||
- [base64](scripting/builtins/libbase64.md)
|
||||
- [bjson, json, toml](scripting/filesystem.md)
|
||||
- [block](scripting/builtins/libblock.md)
|
||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||
- [cameras](scripting/builtins/libcameras.md)
|
||||
- [entities](scripting/builtins/libentities.md)
|
||||
- [file](scripting/builtins/libfile.md)
|
||||
@ -20,6 +22,7 @@ Subsections:
|
||||
- [gfx.text3d](3d-text.md#gfxtext3d-library)
|
||||
- [gui](scripting/builtins/libgui.md)
|
||||
- [hud](scripting/builtins/libhud.md)
|
||||
- [input](scripting/builtins/libinput.md)
|
||||
- [inventory](scripting/builtins/libinventory.md)
|
||||
- [item](scripting/builtins/libitem.md)
|
||||
- [mat4](scripting/builtins/libmat4.md)
|
||||
|
||||
147
doc/en/scripting/builtins/libapp.md
Normal file
147
doc/en/scripting/builtins/libapp.md
Normal file
@ -0,0 +1,147 @@
|
||||
# *app* library
|
||||
|
||||
A library for high-level engine control, available only in script or test mode.
|
||||
|
||||
The script/test name without the path and extension is available as `app.script`. The file path can be obtained as:
|
||||
```lua
|
||||
local filename = "script:"..app.script..".lua"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
```lua
|
||||
app.tick()
|
||||
```
|
||||
|
||||
Performs one tick of the main engine loop.
|
||||
|
||||
```lua
|
||||
app.sleep(time: number)
|
||||
```
|
||||
|
||||
Waits for the specified time in seconds, performing the main engine loop.
|
||||
|
||||
```lua
|
||||
app.sleep_until(
|
||||
-- function that checks the condition for ending the wait
|
||||
predicate: function() -> bool,
|
||||
-- the maximum number of engine loop ticks after which
|
||||
-- a "max ticks exceed" exception will be thrown
|
||||
[optional] max_ticks = 1e9
|
||||
)
|
||||
```
|
||||
|
||||
Waits for the condition checked by the function to be true, performing the main engine loop.
|
||||
|
||||
```lua
|
||||
app.quit()
|
||||
```
|
||||
|
||||
Terminates the engine, printing the call stack to trace the function call location.
|
||||
|
||||
```lua
|
||||
app.reconfig_packs(
|
||||
-- packs to add
|
||||
add_packs: table,
|
||||
-- packs to remove
|
||||
remove_packs: table
|
||||
)
|
||||
```
|
||||
|
||||
Updates the packs configuration, checking its correctness (dependencies and availability of packs).
|
||||
Automatically adds dependencies.
|
||||
|
||||
To remove all packs from the configuration, you can use `pack.get_installed()`:
|
||||
|
||||
```lua
|
||||
app.reconfig_packs({}, pack.get_installed())
|
||||
```
|
||||
|
||||
In this case, `base` will also be removed from the configuration.
|
||||
|
||||
```lua
|
||||
app.config_packs(
|
||||
-- expected set of packs (excluding dependencies)
|
||||
packs: table
|
||||
)
|
||||
```
|
||||
|
||||
Updates the packs configuration, automatically removing unspecified ones, adding those missing in the previous configuration.
|
||||
Uses app.reconfig_packs.
|
||||
|
||||
```lua
|
||||
app.new_world(
|
||||
-- world name
|
||||
name: str,
|
||||
-- generation seed
|
||||
seed: str,
|
||||
-- generator name
|
||||
generator: str
|
||||
)
|
||||
```
|
||||
|
||||
Creates a new world and opens it.
|
||||
|
||||
```lua
|
||||
app.open_world(name: str)
|
||||
```
|
||||
|
||||
Opens a world by name.
|
||||
|
||||
```lua
|
||||
app.reopen_world()
|
||||
```
|
||||
|
||||
Reopens the world.
|
||||
|
||||
```lua
|
||||
app.save_world()
|
||||
```
|
||||
|
||||
Saves the world.
|
||||
|
||||
```lua
|
||||
app.close_world(
|
||||
-- save the world before closing
|
||||
[optional] save_world: bool=false
|
||||
)
|
||||
```
|
||||
|
||||
Closes the world.
|
||||
|
||||
```lua
|
||||
app.delete_world(name: str)
|
||||
```
|
||||
|
||||
Deletes a world by name.
|
||||
|
||||
```lua
|
||||
app.get_version() -> int, int
|
||||
```
|
||||
|
||||
Returns the major and minor versions of the engine.
|
||||
|
||||
```lua
|
||||
app.get_setting(name: str) -> value
|
||||
```
|
||||
|
||||
Returns the value of a setting. Throws an exception if the setting does not exist.
|
||||
|
||||
```lua
|
||||
app.set_setting(name: str, value: value)
|
||||
```
|
||||
|
||||
Sets the value of a setting. Throws an exception if the setting does not exist.
|
||||
|
||||
```lua
|
||||
app.get_setting_info(name: str) -> {
|
||||
-- default value
|
||||
def: value,
|
||||
-- minimum value
|
||||
[only for numeric settings] min: number,
|
||||
-- maximum value
|
||||
[only for numeric settings] max: number
|
||||
}
|
||||
```
|
||||
|
||||
Returns a table with information about a setting. Throws an exception if the setting does not exist.
|
||||
@ -67,12 +67,15 @@ Following three functions return direction vectors based on block rotation.
|
||||
-- Returns X: integer direction vector of the block at specified coordinates.
|
||||
-- Example: no rotation: 1, 0, 0.
|
||||
block.get_X(x: int, y: int, z: int) -> int, int, int
|
||||
block.get_X(id: int, rotation: int) -> int, int, int
|
||||
|
||||
-- Same for axis Y. Default: 0, 1, 0.
|
||||
block.get_Y(x: int, y: int, z: int) -> int, int, int
|
||||
block.get_Y(id: int, rotation: int) -> int, int, int
|
||||
|
||||
-- Same for axis Z. Default: 0, 0, 1.
|
||||
block.get_Z(x: int, y: int, z: int) -> int, int, int
|
||||
block.get_Z(id: int, rotation: int) -> int, int, int
|
||||
|
||||
-- Returns block rotation index based on used profile.
|
||||
block.get_rotation(x: int, y: int, z: int) -> int
|
||||
|
||||
70
doc/en/scripting/builtins/libbyteutil.md
Normal file
70
doc/en/scripting/builtins/libbyteutil.md
Normal file
@ -0,0 +1,70 @@
|
||||
# *byteutil* library
|
||||
|
||||
The library provides functions for working with byte arrays represented as tables or Bytearrays.
|
||||
|
||||
```lua
|
||||
byteutil.pack(format: str, ...) -> Bytearray
|
||||
byteutil.tpack(format: str, ...) -> table
|
||||
```
|
||||
|
||||
Returns a byte array containing the provided values packed according to the format string. The arguments must exactly match the values required by the format.
|
||||
|
||||
The format string consists of special characters and value characters.
|
||||
|
||||
Special characters specify the byte order for the subsequent values:
|
||||
|
||||
| Character | Byte Order |
|
||||
| --------- | ------------------- |
|
||||
| `@` | System |
|
||||
| `=` | System |
|
||||
| `<` | Little-endian |
|
||||
| `>` | Big-endian |
|
||||
| `!` | Network (big-endian)|
|
||||
|
||||
|
||||
Value characters describe the type and size.
|
||||
|
||||
| Character | C++ Equivalent | Lua Type | Size |
|
||||
| --------- | -------------- | -------- | ------- |
|
||||
| `b` | int8_t | number | 1 byte |
|
||||
| `B` | uint8_t | number | 1 byte |
|
||||
| `?` | bool | boolean | 1 byte |
|
||||
| `h` | int16_t | number | 2 bytes |
|
||||
| `H` | uint16_t | number | 2 bytes |
|
||||
| `i` | int32_t | number | 4 bytes |
|
||||
| `I` | uint32_t | number | 4 bytes |
|
||||
| `l` | int64_t | number | 8 bytes |
|
||||
| `L` | uint64_t | number | 8 bytes |
|
||||
|
||||
> [!WARNING]
|
||||
> Due to the absence of an integer type in Lua for values `l` and `L`, only an output size of 8 bytes is guaranteed; the value may differ from what is expected.
|
||||
|
||||
```lua
|
||||
byteutil.unpack(format: str, bytes: table|Bytearray) -> ...
|
||||
```
|
||||
|
||||
Extracts values from a byte array based on a format string.
|
||||
|
||||
Example:
|
||||
|
||||
```lua
|
||||
debug.print(byteutil.tpack('>iBH?', -8, 250, 2019, true))
|
||||
-- outputs:
|
||||
-- debug.print(
|
||||
-- {
|
||||
-- 255,
|
||||
-- 255,
|
||||
-- 255,
|
||||
-- 248,
|
||||
-- 250,
|
||||
-- 7,
|
||||
-- 227,
|
||||
-- 1
|
||||
-- }
|
||||
-- )
|
||||
|
||||
local bytes = byteutil.pack('>iBH?', -8, 250, 2019, true)
|
||||
print(byteutil.unpack('>iBH?', bytes))
|
||||
-- outputs:
|
||||
-- -8 250 2019 true
|
||||
```
|
||||
@ -25,6 +25,12 @@ file.read_bytes(path: str) -> array of integers
|
||||
|
||||
Read file into bytes array.
|
||||
|
||||
```lua
|
||||
file.is_writeable(path: str) -> bool
|
||||
```
|
||||
|
||||
Checks if the specified path is writable.
|
||||
|
||||
```python
|
||||
file.write(path: str, text: str) -> nil
|
||||
```
|
||||
@ -114,3 +120,27 @@ file.read_combined_object(path: str) -> array
|
||||
```
|
||||
|
||||
Combines objects from JSON files of different packs.
|
||||
|
||||
```lua
|
||||
file.name(path: str) --> str
|
||||
```
|
||||
|
||||
Extracts the file name from the path. Example: `world:data/base/config.toml` -> `config.toml`.
|
||||
|
||||
``lua
|
||||
file.stem(path: str) --> str
|
||||
```
|
||||
|
||||
Extracts the file name from the path, removing the extension. Example: `world:data/base/config.toml` -> `config`.
|
||||
|
||||
```lua
|
||||
file.ext(path: str) --> str
|
||||
```
|
||||
|
||||
Extracts the extension from the path. Example: `world:data/base/config.toml` -> `toml`.
|
||||
|
||||
```lua
|
||||
file.prefix(path: str) --> str
|
||||
```
|
||||
|
||||
Extracts the entry point (prefix) from the path. Example: `world:data/base/config.toml` -> `world`.
|
||||
|
||||
@ -61,3 +61,34 @@ gui.escape_markup(
|
||||
```
|
||||
|
||||
Escapes markup in text.
|
||||
|
||||
```lua
|
||||
gui.confirm(
|
||||
-- message (does not translate automatically, use gui.str(...))
|
||||
message: str,
|
||||
-- function called upon confirmation
|
||||
on_confirm: function() -> nil,
|
||||
-- function called upon denial/cancellation
|
||||
[optional] on_deny: function() -> nil,
|
||||
-- text for the confirmation button (default: "Yes")
|
||||
-- use an empty string for the default value if you want to specify no_text.
|
||||
[optional] yes_text: str,
|
||||
-- text for the denial button (default: "No")
|
||||
[optional] no_text: str,
|
||||
)
|
||||
```
|
||||
|
||||
Requests confirmation from the user for an action. **Does not** stop code execution.
|
||||
|
||||
```lua
|
||||
gui.load_document(
|
||||
-- Path to the xml file of the page. Example: `core:layouts/pages/main.xml`
|
||||
path: str,
|
||||
-- Name (id) of the document. Example: `core:pages/main`
|
||||
name: str
|
||||
-- Table of parameters passed to the on_open event
|
||||
args: table
|
||||
) --> str
|
||||
```
|
||||
|
||||
Loads a UI document with its script, returns the name of the document if successfully loaded.
|
||||
|
||||
89
doc/en/scripting/builtins/libinput.md
Normal file
89
doc/en/scripting/builtins/libinput.md
Normal file
@ -0,0 +1,89 @@
|
||||
# *input* library
|
||||
|
||||
```lua
|
||||
input.keycode(keyname: str) --> int
|
||||
```
|
||||
|
||||
Returns key code or -1 if unknown
|
||||
|
||||
```lua
|
||||
input.mousecode(mousename: str) --> int
|
||||
```
|
||||
|
||||
Returns mouse button code or -1 if unknown
|
||||
|
||||
```lua
|
||||
input.add_callback(bindname: str, callback: function)
|
||||
```
|
||||
|
||||
Add binding activation callback. Example:
|
||||
|
||||
```lua
|
||||
input.add_callback("hud.inventory", function ()
|
||||
print("Inventory open key pressed")
|
||||
end)
|
||||
```
|
||||
|
||||
Callback may be added to a key.
|
||||
|
||||
```lua
|
||||
input.add_callback("key:space", function ()
|
||||
print("Space pressed")
|
||||
end)
|
||||
```
|
||||
|
||||
You can also bind the function lifetime to the UI container instead of the HUD.
|
||||
In that case, `input.add_callback` may be used until the `on_hud_open` is called.
|
||||
|
||||
```lua
|
||||
input.add_callback("key:escape", function ()
|
||||
print("NO")
|
||||
return true -- prevents previously assigned functions from being called
|
||||
end, document.root)
|
||||
```
|
||||
|
||||
```lua
|
||||
input.get_mouse_pos() --> {int, int}
|
||||
```
|
||||
|
||||
Returns cursor screen position.
|
||||
|
||||
```lua
|
||||
input.get_bindings() --> strings array
|
||||
```
|
||||
|
||||
Returns all binding names.
|
||||
|
||||
```lua
|
||||
input.get_binding_text(bindname: str) --> str
|
||||
```
|
||||
|
||||
Returns text representation of button by binding name.
|
||||
|
||||
```lua
|
||||
input.is_active(bindname: str) --> bool
|
||||
```
|
||||
|
||||
Checks if the binding is active.
|
||||
|
||||
```lua
|
||||
input.set_enabled(bindname: str, flag: bool)
|
||||
```
|
||||
|
||||
Enables/disables binding until leaving the world.
|
||||
|
||||
```lua
|
||||
input.is_pressed(code: str) --> bool
|
||||
```
|
||||
|
||||
Checks input activity using a code consisting of:
|
||||
- input type: *key* or *mouse*
|
||||
- input code: [key name](#key names) or mouse button name (left, middle, right)
|
||||
|
||||
Example:
|
||||
```lua
|
||||
if input.is_pressed("key:enter") then
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
@ -32,6 +32,21 @@ inventory.size(invid: int) -> int
|
||||
-- Returns remaining count if could not to add fully.
|
||||
inventory.add(invid: int, itemid: int, count: int) -> int
|
||||
|
||||
-- Returns the index of the first matching slot in the given range.
|
||||
-- If no matching slot was found, returns nil
|
||||
inventory.find_by_item(
|
||||
-- inventory id
|
||||
invid: int,
|
||||
-- item id
|
||||
itemid: int,
|
||||
-- [optional] index of the slot range start (from 0)
|
||||
range_begin: int,
|
||||
-- [optional] index of the slot range end (from 0)
|
||||
range_end: int,
|
||||
-- [optional] minimum item count in the slot
|
||||
min_count: int = 1
|
||||
) -> int
|
||||
|
||||
-- Returns block inventory ID or 0.
|
||||
inventory.get_block(x: int, y: int, z: int) -> int
|
||||
|
||||
|
||||
@ -16,6 +16,11 @@ end)
|
||||
|
||||
-- A variant for binary files, with a byte array instead of a string in the response.
|
||||
network.get_binary(url: str, callback: function(table|ByteArray))
|
||||
|
||||
-- Performs a POST request to the specified URL.
|
||||
-- Currently, only `Content-Type: application/json` is supported
|
||||
-- After receiving the response, passes the text to the callback function.
|
||||
network.post(url: str, data: table, callback: function(str))
|
||||
```
|
||||
|
||||
## TCP Connections
|
||||
@ -54,6 +59,9 @@ socket:recv(
|
||||
-- Closes the connection
|
||||
socket:close()
|
||||
|
||||
-- Returns the number of data bytes available for reading
|
||||
socket:available() --> int
|
||||
|
||||
-- Checks that the socket exists and is not closed.
|
||||
socket:is_alive() --> bool
|
||||
|
||||
|
||||
@ -76,14 +76,15 @@ pack.get_base_packs() -> strings array
|
||||
|
||||
Returns the id of all base packages (non-removeable)
|
||||
|
||||
```python
|
||||
```lua
|
||||
pack.get_info(packid: str) -> {
|
||||
id: str,
|
||||
title: str,
|
||||
creator: str,
|
||||
description: str,
|
||||
version: str,
|
||||
icon: str,
|
||||
path: str,
|
||||
icon: str, -- not available in headless mode
|
||||
dependencies: optional strings array
|
||||
}
|
||||
```
|
||||
@ -95,3 +96,15 @@ Returns information about the pack (not necessarily installed).
|
||||
- `?` - optional
|
||||
- `~` - weak
|
||||
for example `!teal`
|
||||
|
||||
To obtain information about multiple packs, use table of ids to avoid re-scanning:one
|
||||
|
||||
```lua
|
||||
pack.get_info(packids: table) -> {id={...}, id2={...}, ...}
|
||||
```
|
||||
|
||||
```lua
|
||||
pack.assemble(packis: table) -> table
|
||||
```
|
||||
|
||||
Checks the configuration for correctness and adds dependencies, returning the complete configuration.
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
# *player* library
|
||||
|
||||
```lua
|
||||
player.create(name: str) -> int
|
||||
```
|
||||
|
||||
Creates a player and returns id.
|
||||
|
||||
```lua
|
||||
player.delete(id: int)
|
||||
```
|
||||
|
||||
Deletes a player by id.
|
||||
|
||||
```lua
|
||||
player.get_pos(playerid: int) -> number, number, number
|
||||
```
|
||||
@ -70,6 +82,13 @@ player.set_instant_destruction(playerid: int, bool)
|
||||
|
||||
Getter and setter for instant destruction of blocks when the `player.destroy` binding is activated.
|
||||
|
||||
```lua
|
||||
player.is_loading_chunks(playerid: int) -> bool
|
||||
player.set_loading_chunks(playerid: int, bool)
|
||||
```
|
||||
|
||||
Getter and setter of the property that determines whether the player is loading chunks.
|
||||
|
||||
``` lua
|
||||
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
|
||||
player.get_spawnpoint(playerid: int) -> number, number, number
|
||||
@ -84,6 +103,12 @@ player.get_name(playerid: int) -> str
|
||||
|
||||
Player name setter and getter
|
||||
|
||||
```lua
|
||||
player.set_selected_slot(playerid: int, slotid: int)
|
||||
```
|
||||
|
||||
Sets the selected slot index
|
||||
|
||||
```lua
|
||||
player.get_selected_block(playerid: int) -> x,y,z
|
||||
```
|
||||
|
||||
@ -36,14 +36,15 @@ world.get_seed() -> int
|
||||
-- Returns generator name.
|
||||
world.get_generator() -> str
|
||||
|
||||
-- Proves that this is the current time during the day
|
||||
-- from 0.333(8 am) to 0.833(8 pm).
|
||||
world.is_day() -> boolean
|
||||
-- Checks the existence of a world by name.
|
||||
world.exists(name: str) -> bool
|
||||
|
||||
-- Checks that it is the current time at night
|
||||
-- from 0.833(8 pm) to 0.333(8 am).
|
||||
-- Checks if the current time is daytime. From 0.333(8am) to 0.833(8pm).
|
||||
world.is_day() -> bool
|
||||
|
||||
-- Checks if the current time is nighttime. From 0.833(8pm) to 0.333(8am).
|
||||
world.is_night() -> bool
|
||||
|
||||
-- Checks the existence of a world by name.
|
||||
world.exists() -> bool
|
||||
-- Returns the total number of chunks loaded into memory
|
||||
world.count_chunks() -> int
|
||||
```
|
||||
|
||||
@ -48,6 +48,7 @@ Properties that apply to all elements:
|
||||
| tooltip | string | yes | yes | tooltip text |
|
||||
| tooltipDelay | float | yes | yes | tooltip delay |
|
||||
| contentOffset | vec2 | yes | *no* | element content offset |
|
||||
| cursor | string | yes | yes | cursor displayed on hover |
|
||||
|
||||
Common element methods:
|
||||
|
||||
|
||||
@ -25,70 +25,4 @@ packid.binding.name="inputtype:codename"
|
||||
|
||||
## *input* library
|
||||
|
||||
```python
|
||||
input.keycode(keyname: str) -> int
|
||||
```
|
||||
|
||||
Returns key code or -1 if unknown
|
||||
|
||||
```python
|
||||
input.mousecode(mousename: str) -> int
|
||||
```
|
||||
|
||||
Returns mouse button code or -1 if unknown
|
||||
|
||||
```python
|
||||
input.add_callback(bindname: str, callback: function)
|
||||
```
|
||||
|
||||
Add binding activation callback. Example:
|
||||
```lua
|
||||
input.add_callback("hud.inventory", function ()
|
||||
print("Inventory open key pressed")
|
||||
end)
|
||||
```
|
||||
|
||||
```python
|
||||
input.get_mouse_pos() -> {int, int}
|
||||
```
|
||||
|
||||
Returns cursor screen position.
|
||||
|
||||
```python
|
||||
input.get_bindings() -> strings array
|
||||
```
|
||||
|
||||
Returns all binding names.
|
||||
|
||||
```python
|
||||
input.get_binding_text(bindname: str) -> str
|
||||
```
|
||||
|
||||
Returns text representation of button by binding name.
|
||||
|
||||
```python
|
||||
input.is_active(bindname: str) -> bool
|
||||
```
|
||||
|
||||
Checks if the binding is active.
|
||||
|
||||
```python
|
||||
input.set_enabled(bindname: str, flag: bool)
|
||||
```
|
||||
|
||||
Enables/disables binding until leaving the world.
|
||||
|
||||
```python
|
||||
input.is_pressed(code: str) -> bool
|
||||
```
|
||||
|
||||
Checks input activity using a code consisting of:
|
||||
- input type: *key* or *mouse*
|
||||
- input code: [key name](#key names) or mouse button name (left, middle, right)
|
||||
|
||||
Example:
|
||||
```lua
|
||||
if input.is_pressed("key:enter") then
|
||||
...
|
||||
end
|
||||
```
|
||||
See [*input* library](builtins/libinput.md)
|
||||
|
||||
@ -44,6 +44,7 @@ Examples:
|
||||
- `gravity` - automatic positioning of the element in the container. (Does not work in automatic containers like panel). Values: *top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right*.
|
||||
- `z-index` - determines the order of elements, with a larger value it will overlap elements with a smaller one.
|
||||
- `interactive` - if false, hovering over the element and all sub-elements will be ignored.
|
||||
- `cursor` - the cursor displayed when hovering over the element (arrow/text/pointer/crosshair/ew-resize/ns-resize/...).
|
||||
|
||||
# Template attributes
|
||||
|
||||
|
||||
@ -224,3 +224,39 @@
|
||||
```
|
||||
|
||||
Пример: [пользовательские свойства пака **base**](../../res/content/base/config/user-props.toml).
|
||||
|
||||
## Свойства, вводимые паком `base`
|
||||
|
||||
### Прочность - *base:durability*
|
||||
|
||||
Время разрушения блока без инструментов и эффектов в секундах.
|
||||
|
||||
### Лут - *base:loot*
|
||||
|
||||
Список таблиц со свойствами:
|
||||
|
||||
```json
|
||||
{
|
||||
"item": "пак:предмет",
|
||||
"min": 1,
|
||||
"max": 3,
|
||||
"chance": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
- count равен 1 по-умолчанию. Не нужно указывать если указаны `min` и `max`.
|
||||
- min, max - минимальное и максимальное количество предмета.
|
||||
- chance - вероятность выпадения предмета. По-умолчанию: 1.0.
|
||||
|
||||
Следует учитывать, что в item указывается именно предмет. Т.е. чтобы указать предмет блока, нужно добавить `.item` после имени блока.
|
||||
Пример: `base:dirt.item`.
|
||||
|
||||
Для генерации лута следует использовать функцию `block_loot(block_id: int)` в модуле `base:util`.
|
||||
|
||||
## Методы
|
||||
|
||||
Методы используются для управлением перезаписью свойств при расширении блока другими паками.
|
||||
|
||||
### `имя_свойства@append`
|
||||
|
||||
Добавляет элементы в конец списка, вместо его полной перезаписи.
|
||||
|
||||
@ -9,9 +9,11 @@
|
||||
- [Свойства и методы UI элементов](scripting/ui.md)
|
||||
- [Сущности и компоненты](scripting/ecs.md)
|
||||
- [Библиотеки](#)
|
||||
- [app](scripting/builtins/libapp.md)
|
||||
- [base64](scripting/builtins/libbase64.md)
|
||||
- [bjson, json, toml](scripting/filesystem.md)
|
||||
- [block](scripting/builtins/libblock.md)
|
||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||
- [cameras](scripting/builtins/libcameras.md)
|
||||
- [entities](scripting/builtins/libentities.md)
|
||||
- [file](scripting/builtins/libfile.md)
|
||||
@ -20,6 +22,7 @@
|
||||
- [gfx.text3d](3d-text.md#библиотека-gfxtext3d)
|
||||
- [gui](scripting/builtins/libgui.md)
|
||||
- [hud](scripting/builtins/libhud.md)
|
||||
- [input](scripting/builtins/libinput.md)
|
||||
- [inventory](scripting/builtins/libinventory.md)
|
||||
- [item](scripting/builtins/libitem.md)
|
||||
- [mat4](scripting/builtins/libmat4.md)
|
||||
|
||||
148
doc/ru/scripting/builtins/libapp.md
Normal file
148
doc/ru/scripting/builtins/libapp.md
Normal file
@ -0,0 +1,148 @@
|
||||
# Библиотека *app*
|
||||
|
||||
Библиотека для высокоуровневого управления работой движка, доступная только в режиме сценария или теста.
|
||||
|
||||
Имя сценария/теста без пути и расширения доступен как `app.script`. Путь к файлу можно получить как:
|
||||
```lua
|
||||
local filename = "script:"..app.script..".lua"
|
||||
```
|
||||
|
||||
## Функции
|
||||
|
||||
```lua
|
||||
app.tick()
|
||||
```
|
||||
|
||||
Выполняет один такт основного цикла движка.
|
||||
|
||||
```lua
|
||||
app.sleep(time: number)
|
||||
```
|
||||
|
||||
Ожидает указанное время в секундах, выполняя основной цикл движка.
|
||||
|
||||
```lua
|
||||
app.sleep_until(
|
||||
-- функция, проверяющее условия завершения ожидания
|
||||
predicate: function() -> bool,
|
||||
-- максимальное количество тактов цикла движка, после истечения которых
|
||||
-- будет брошено исключение "max ticks exceed"
|
||||
[опционально] max_ticks = 1e9
|
||||
)
|
||||
```
|
||||
|
||||
Ожидает истинности утверждения (условия), проверяемого функцией, выполнячя основной цикл движка.
|
||||
|
||||
```lua
|
||||
app.quit()
|
||||
```
|
||||
|
||||
Завершает выполнение движка, выводя стек вызовов для ослеживания места вызова функции.
|
||||
|
||||
```lua
|
||||
app.reconfig_packs(
|
||||
-- добавляемые паки
|
||||
add_packs: table,
|
||||
-- удаляемые паки
|
||||
remove_packs: table
|
||||
)
|
||||
```
|
||||
|
||||
Обновляет конфигурацию паков, проверяя её корректность (зависимости и доступность паков).
|
||||
Автоматически добавляет зависимости.
|
||||
|
||||
Для удаления всех паков из конфигурации можно использовать `pack.get_installed()`:
|
||||
|
||||
```lua
|
||||
app.reconfig_packs({}, pack.get_installed())
|
||||
```
|
||||
|
||||
В этом случае из конфигурации будет удалён и `base`.
|
||||
|
||||
```lua
|
||||
app.config_packs(
|
||||
-- ожидаемый набор паков (без учёта зависимостей)
|
||||
packs: table
|
||||
)
|
||||
```
|
||||
|
||||
Обновляет конфигурацию паков, автоматически удаляя лишние, добавляя отсутствующие в прошлой конфигурации.
|
||||
Использует app.reconfig_packs.
|
||||
|
||||
```lua
|
||||
app.new_world(
|
||||
-- название мира
|
||||
name: str,
|
||||
-- зерно генерации
|
||||
seed: str,
|
||||
-- название генератора
|
||||
generator: str
|
||||
)
|
||||
```
|
||||
|
||||
Создаёт новый мир и открывает его.
|
||||
|
||||
```lua
|
||||
app.open_world(name: str)
|
||||
```
|
||||
|
||||
Открывает мир по названию.
|
||||
|
||||
```lua
|
||||
app.reopen_world()
|
||||
```
|
||||
|
||||
Переоткрывает мир.
|
||||
|
||||
```lua
|
||||
app.save_world()
|
||||
```
|
||||
|
||||
Сохраняет мир.
|
||||
|
||||
```lua
|
||||
app.close_world(
|
||||
-- сохранить мир перед закрытием
|
||||
[опционально] save_world: bool=false
|
||||
)
|
||||
```
|
||||
|
||||
Закрывает мир.
|
||||
|
||||
```lua
|
||||
app.delete_world(name: str)
|
||||
```
|
||||
|
||||
Удаляет мир по названию.
|
||||
|
||||
```lua
|
||||
app.get_version() -> int, int
|
||||
```
|
||||
|
||||
Возвращает мажорную и минорную версии движка.
|
||||
|
||||
```lua
|
||||
app.get_setting(name: str) -> value
|
||||
```
|
||||
|
||||
Возвращает значение настройки. Бросает исключение, если настройки не существует.
|
||||
|
||||
```lua
|
||||
app.set_setting(name: str, value: value)
|
||||
```
|
||||
|
||||
Устанавливает значение настройки. Бросает исключение, если настройки не существует.
|
||||
|
||||
|
||||
```lua
|
||||
app.get_setting_info(name: str) -> {
|
||||
-- значение по-умолчанию
|
||||
def: value
|
||||
-- минимальное значение
|
||||
[только числовые настройки] min: number,
|
||||
-- максимальное значение
|
||||
[только числовые настройки] max: number
|
||||
}
|
||||
```
|
||||
|
||||
Возвращает таблицу с информацией о настройке. Бросает исключение, если настройки не существует.
|
||||
@ -90,12 +90,15 @@ block.raycast(start: vec3, dir: vec3, max_distance: number, [опциональ
|
||||
-- Возвращает целочисленный единичный вектор X блока на указанных координатах с учётом его вращения (три целых числа).
|
||||
-- Если поворот отсутствует, возвращает 1, 0, 0
|
||||
block.get_X(x: int, y: int, z: int) -> int, int, int
|
||||
block.get_X(id: int, rotation: int) -> int, int, int
|
||||
|
||||
-- То же, но для оси Y (по-умолчанию 0, 1, 0)
|
||||
block.get_Y(x: int, y: int, z: int) -> int, int, int
|
||||
block.get_Y(id: int, rotation: int) -> int, int, int
|
||||
|
||||
-- То же, но для оси Z (по-умолчанию 0, 0, 1)
|
||||
block.get_Z(x: int, y: int, z: int) -> int, int, int
|
||||
block.get_Z(id: int, rotation: int) -> int, int, int
|
||||
|
||||
-- Возвращает индекс поворота блока в его профиле вращения (не превышает 7).
|
||||
block.get_rotation(x: int, y: int, z: int) -> int
|
||||
|
||||
71
doc/ru/scripting/builtins/libbyteutil.md
Normal file
71
doc/ru/scripting/builtins/libbyteutil.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Библиотека *byteutil*
|
||||
|
||||
Библиотека предоставляет функции для работы с массивами байт, представленными в виде таблиц или Bytearray.
|
||||
|
||||
```lua
|
||||
byteutil.pack(format: str, ...) -> Bytearray
|
||||
byteutil.tpack(format: str, ...) -> table
|
||||
```
|
||||
|
||||
Возвращает массив байт, содержащий переданные значения, упакованные в соответствии со строкой формата. Аргументы должны точно соответствовать значениям, требуемым форматом.
|
||||
|
||||
Строка формата состоит из специальных символов и символов значений.
|
||||
|
||||
Специальные символы позволяют указать порядок байт для последующих значений:
|
||||
|
||||
| Символ | Порядок байт |
|
||||
| ------ | -------------------- |
|
||||
| `@` | Системный |
|
||||
| `=` | Системный |
|
||||
| `<` | Little-endian |
|
||||
| `>` | Big-endian |
|
||||
| `!` | Сетевой (big-endian) |
|
||||
|
||||
|
||||
Символы значений описывают тип и размер.
|
||||
|
||||
| Символ | Аналог в С++ | Тип Lua | Размер |
|
||||
| ------ | ------------ | -------- | ------- |
|
||||
| `b` | int8_t | number | 1 байт |
|
||||
| `B` | uint8_t | number | 1 байт |
|
||||
| `?` | bool | boolean | 1 байт |
|
||||
| `h` | int16_t | number | 2 байта |
|
||||
| `H` | uint16_t | number | 2 байта |
|
||||
| `i` | int32_t | number | 4 байта |
|
||||
| `I` | uint32_t | number | 4 байта |
|
||||
| `l` | int64_t | number | 8 байта |
|
||||
| `L` | uint64_t | number | 8 байта |
|
||||
|
||||
> [!WARNING]
|
||||
> Из-за отсутствия в Lua целочисленного типа для значений `l` и `L` гарантируется
|
||||
> только выходной размер в 8 байт, значение может отличаться от ожидаемого.
|
||||
|
||||
```lua
|
||||
byteutil.unpack(format: str, bytes: table|Bytearray) -> ...
|
||||
```
|
||||
|
||||
Извлекает значения из массива байт, ориентируясь на строку формата.
|
||||
|
||||
Пример:
|
||||
|
||||
```lua
|
||||
debug.print(byteutil.tpack('>iBH?', -8, 250, 2019, true))
|
||||
-- выводит:
|
||||
-- debug.print(
|
||||
-- {
|
||||
-- 255,
|
||||
-- 255,
|
||||
-- 255,
|
||||
-- 248,
|
||||
-- 250,
|
||||
-- 7,
|
||||
-- 227,
|
||||
-- 1
|
||||
-- }
|
||||
-- )
|
||||
|
||||
local bytes = byteutil.pack('>iBH?', -8, 250, 2019, true)
|
||||
debug.print(byteutil.unpack('>iBH?', bytes))
|
||||
-- выводит:
|
||||
-- -8 250 2019 true
|
||||
```
|
||||
@ -25,6 +25,12 @@ file.read_bytes(путь: str) -> array of integers
|
||||
|
||||
Читает файл в массив байт.
|
||||
|
||||
```lua
|
||||
file.is_writeable(путь: str) -> bool
|
||||
```
|
||||
|
||||
Проверяет, доступно ли право записи по указанному пути.
|
||||
|
||||
```python
|
||||
file.write(путь: str, текст: str) -> nil
|
||||
```
|
||||
@ -114,3 +120,27 @@ file.read_combined_object(путь: str) -> массив
|
||||
```
|
||||
|
||||
Совмещает объекты из JSON файлов разных паков.
|
||||
|
||||
```lua
|
||||
file.name(путь: str) --> str
|
||||
```
|
||||
|
||||
Извлекает имя файла из пути. Пример: `world:data/base/config.toml` -> `config.toml`.
|
||||
|
||||
```lua
|
||||
file.stem(путь: str) --> str
|
||||
```
|
||||
|
||||
Извлекает имя файла из пути, удаляя расширение. Пример: `world:data/base/config.toml` -> `config`.
|
||||
|
||||
```lua
|
||||
file.ext(путь: str) --> str
|
||||
```
|
||||
|
||||
Извлекает расширение из пути. Пример: `world:data/base/config.toml` -> `toml`.
|
||||
|
||||
```lua
|
||||
file.prefix(путь: str) --> str
|
||||
```
|
||||
|
||||
Извлекает точку входа (префикс) из пути. Пример: `world:data/base/config.toml` -> `world`.
|
||||
|
||||
@ -58,3 +58,34 @@ gui.escape_markup(
|
||||
```
|
||||
|
||||
Экранирует разметку в тексте.
|
||||
|
||||
```lua
|
||||
gui.confirm(
|
||||
-- сообщение (не переводится автоматически, используйте gui.str(...))
|
||||
message: str,
|
||||
-- функция, вызываемая при подтвержении
|
||||
on_confirm: function() -> nil,
|
||||
-- функция, вызываемая при отказе/отмене
|
||||
[опционально] on_deny: function() -> nil,
|
||||
-- текст кнопки подтвержения (по-умолчанию: "Да")
|
||||
-- используйте пустую строку для значения по-умолчанию, если нужно указать no_text.
|
||||
[опционально] yes_text: str,
|
||||
-- текст кнопки отказа (по-умолчанию: "Нет")
|
||||
[опционально] no_text: str,
|
||||
)
|
||||
```
|
||||
|
||||
Запрашивает у пользователя подтверждение действия. **Не** останавливает выполнение кода.
|
||||
|
||||
```lua
|
||||
gui.load_document(
|
||||
-- Путь к xml файлу страницы. Пример: `core:layouts/pages/main.xml`
|
||||
path: str,
|
||||
-- Имя (id) документа. Пример: `core:pages/main`
|
||||
name: str
|
||||
-- Таблица параметров, передаваемых в событие on_open
|
||||
args: table
|
||||
) --> str
|
||||
```
|
||||
|
||||
Загружает UI документ с его скриптом, возвращает имя документа, если успешно загружен.
|
||||
|
||||
88
doc/ru/scripting/builtins/libinput.md
Normal file
88
doc/ru/scripting/builtins/libinput.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Библиотека *input*
|
||||
|
||||
```lua
|
||||
input.keycode(keyname: str) --> int
|
||||
```
|
||||
|
||||
Возвращает код клавиши по имени, либо -1
|
||||
|
||||
```lua
|
||||
input.mousecode(mousename: str) --> int
|
||||
```
|
||||
|
||||
Возвращает код кнопки мыши по имени, либо -1
|
||||
|
||||
```lua
|
||||
input.add_callback(bindname: str, callback: function)
|
||||
```
|
||||
|
||||
Назначает функцию, которая будет вызываться при активации привязки. Пример:
|
||||
|
||||
```lua
|
||||
input.add_callback("hud.inventory", function ()
|
||||
print("Inventory open key pressed")
|
||||
end)
|
||||
```
|
||||
|
||||
Можно назначить функцию на нажатие клавиши.
|
||||
|
||||
```lua
|
||||
input.add_callback("key:space", function ()
|
||||
print("Space pressed")
|
||||
end)
|
||||
```
|
||||
|
||||
Также можно привязать время жизни функции к UI контейнеру, вместо HUD.
|
||||
В таком случае, `input.add_callback` можно использовать до вызова `on_hud_open`.
|
||||
|
||||
```lua
|
||||
input.add_callback("key:escape", function ()
|
||||
print("NO")
|
||||
return true -- предотвращает вызов назначенных ранее функций
|
||||
end, document.root)
|
||||
```
|
||||
|
||||
```lua
|
||||
input.get_mouse_pos() --> {int, int}
|
||||
```
|
||||
|
||||
Возвращает позицию курсора на экране.
|
||||
|
||||
```lua
|
||||
input.get_bindings() --> массив строк
|
||||
```
|
||||
|
||||
Возвращает названия всех доступных привязок.
|
||||
|
||||
```lua
|
||||
input.get_binding_text(bindname: str) --> str
|
||||
```
|
||||
|
||||
Возвращает текстовое представление кнопки по имени привязки.
|
||||
|
||||
```lua
|
||||
input.is_active(bindname: str) --> bool
|
||||
```
|
||||
|
||||
Проверяет активность привязки.
|
||||
|
||||
```lua
|
||||
input.set_enabled(bindname: str, flag: bool)
|
||||
```
|
||||
|
||||
Включает/выключает привязку до выхода из мира.
|
||||
|
||||
```lua
|
||||
input.is_pressed(code: str) --> bool
|
||||
```
|
||||
|
||||
Проверяет активность ввода по коду, состоящему из:
|
||||
- типа ввода: key (клавиша) или mouse (кнопка мыши)
|
||||
- код ввода: [имя клавиши](#имена-клавиш) или имя кнопки мыши (left, middle, right)
|
||||
|
||||
Пример:
|
||||
```lua
|
||||
if input.is_pressed("key:enter") then
|
||||
...
|
||||
end
|
||||
```
|
||||
@ -38,6 +38,21 @@ inventory.add(
|
||||
count: int
|
||||
) -> int
|
||||
|
||||
-- Возвращает индекс первого подходящего под критерии слота в заданном диапазоне.
|
||||
-- Если подходящий слот не был найден, возвращает nil
|
||||
inventory.find_by_item(
|
||||
-- id инвентаря
|
||||
invid: int,
|
||||
-- id предмета
|
||||
itemid: int,
|
||||
-- [опционально] индекс начала диапазона слотов (c 0)
|
||||
range_begin: int,
|
||||
-- [опционально] индекс конца диапазона слотов (c 0)
|
||||
range_end: int,
|
||||
-- [опционально] минимальное количество предмета в слоте
|
||||
min_count: int = 1
|
||||
) -> int
|
||||
|
||||
-- Функция возвращает id инвентаря блока.
|
||||
-- Если блок не может иметь инвентарь - возвращает 0.
|
||||
inventory.get_block(x: int, y: int, z: int) -> int
|
||||
|
||||
@ -16,6 +16,11 @@ end)
|
||||
|
||||
-- Вариант для двоичных файлов, с массивом байт вместо строки в ответе.
|
||||
network.get_binary(url: str, callback: function(table|ByteArray))
|
||||
|
||||
-- Выполняет POST запрос к указанному URL.
|
||||
-- На данный момент реализована поддержка только `Content-Type: application/json`
|
||||
-- После получения ответа, передаёт текст в функцию callback.
|
||||
network.post(url: str, data: table, callback: function(str))
|
||||
```
|
||||
|
||||
## TCP-Соединения
|
||||
@ -54,6 +59,9 @@ socket:recv(
|
||||
-- Закрывает соединение
|
||||
socket:close()
|
||||
|
||||
-- Возвращает количество доступных для чтения байт данных
|
||||
socket:available() --> int
|
||||
|
||||
-- Проверяет, что сокет существует и не закрыт.
|
||||
socket:is_alive() --> bool
|
||||
|
||||
|
||||
@ -63,14 +63,15 @@ pack.get_base_packs() -> массив строк
|
||||
|
||||
Возвращает id всех базовых паков (неудаляемых)
|
||||
|
||||
```python
|
||||
```lua
|
||||
pack.get_info(packid: str) -> {
|
||||
id: str,
|
||||
title: str,
|
||||
creator: str,
|
||||
description: str,
|
||||
version: str,
|
||||
icon: str,
|
||||
path: str,
|
||||
icon: str, -- отсутствует в headless режиме
|
||||
dependencies: опциональный массив строк
|
||||
}
|
||||
```
|
||||
@ -82,3 +83,16 @@ pack.get_info(packid: str) -> {
|
||||
- `?` - optional
|
||||
- `~` - weak
|
||||
например `!teal`
|
||||
|
||||
Для получения информации о нескольких паках используйте таблицу id, чтобы не
|
||||
производить сканирование для каждого пака:
|
||||
|
||||
```lua
|
||||
pack.get_info(packids: table) -> {id={...}, id2={...}, ...}
|
||||
```
|
||||
|
||||
```lua
|
||||
pack.assemble(packis: table) -> table
|
||||
```
|
||||
|
||||
Проверяет корректность конфигурации и добавляет зависимости, возвращая полную.
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
# Библиотека *player*
|
||||
|
||||
```lua
|
||||
player.create(name: str) -> int
|
||||
```
|
||||
|
||||
Создаёт игрока и возвращает его id.
|
||||
|
||||
```lua
|
||||
player.delete(id: int)
|
||||
```
|
||||
|
||||
Удаляет игрока по id.
|
||||
|
||||
```lua
|
||||
player.get_pos(playerid: int) -> number, number, number
|
||||
```
|
||||
@ -70,6 +82,13 @@ player.set_instant_destruction(playerid: int, bool)
|
||||
|
||||
Геттер и сеттер мнгновенного разрушения блоков при активации привязки `player.destroy`.
|
||||
|
||||
```lua
|
||||
player.is_loading_chunks(playerid: int) -> bool
|
||||
player.set_loading_chunks(playerid: int, bool)
|
||||
```
|
||||
|
||||
Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг.
|
||||
|
||||
```lua
|
||||
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
|
||||
player.get_spawnpoint(playerid: int) -> number, number, number
|
||||
@ -84,6 +103,12 @@ player.get_name(playerid: int) -> str
|
||||
|
||||
Сеттер и геттер имени игрока
|
||||
|
||||
```lua
|
||||
player.set_selected_slot(playerid: int, slotid: int)
|
||||
```
|
||||
|
||||
Устанавливает индекс выбранного слота
|
||||
|
||||
```lua
|
||||
player.get_selected_block(playerid: int) -> x,y,z
|
||||
```
|
||||
|
||||
@ -36,11 +36,14 @@ world.get_seed() -> int
|
||||
world.get_generator() -> str
|
||||
|
||||
-- Проверяет существование мира по имени.
|
||||
world.exists() -> bool
|
||||
world.exists(name: str) -> bool
|
||||
|
||||
-- Проверяет является ли текущее время днём. От 0.333(8 утра) до 0.833(8 вечера).
|
||||
world.is_day() -> bool
|
||||
|
||||
-- Проверяет является ли текущее время ночью. От 0.833(8 вечера) до 0.333(8 утра).
|
||||
world.is_night() -> bool
|
||||
|
||||
-- Возвращает общее количество загруженных в память чанков
|
||||
world.count_chunks() -> int
|
||||
```
|
||||
|
||||
@ -48,6 +48,7 @@ document["worlds-panel"]:clear()
|
||||
| tooltip | string | да | да | текст всплывающей подсказки |
|
||||
| tooltipDelay | float | да | да | задержка всплывающей подсказки |
|
||||
| contentOffset | vec2 | да | *нет* | смещение содержимого |
|
||||
| cursor | string | да | да | курсор, отображаемый при наведении |
|
||||
|
||||
Общие методы элементов:
|
||||
|
||||
|
||||
@ -23,70 +23,4 @@ packid.binding.name="inputtype:codename"
|
||||
|
||||
## Библиотека input
|
||||
|
||||
```python
|
||||
input.keycode(keyname: str) -> int
|
||||
```
|
||||
|
||||
Возвращает код клавиши по имени, либо -1
|
||||
|
||||
```python
|
||||
input.mousecode(mousename: str) -> int
|
||||
```
|
||||
|
||||
Возвращает код кнопки мыши по имени, либо -1
|
||||
|
||||
```python
|
||||
input.add_callback(bindname: str, callback: function)
|
||||
```
|
||||
|
||||
Назначает функцию, которая будет вызываться при активации привязки. Пример:
|
||||
```lua
|
||||
input.add_callback("hud.inventory", function ()
|
||||
print("Inventory open key pressed")
|
||||
end)
|
||||
```
|
||||
|
||||
```python
|
||||
input.get_mouse_pos() -> {int, int}
|
||||
```
|
||||
|
||||
Возвращает позицию курсора на экране.
|
||||
|
||||
```python
|
||||
input.get_bindings() -> массив строк
|
||||
```
|
||||
|
||||
Возвращает названия всех доступных привязок.
|
||||
|
||||
```python
|
||||
input.get_binding_text(bindname: str) -> str
|
||||
```
|
||||
|
||||
Возвращает текстовое представление кнопки по имени привязки.
|
||||
|
||||
```python
|
||||
input.is_active(bindname: str) -> bool
|
||||
```
|
||||
|
||||
Проверяет активность привязки.
|
||||
|
||||
```python
|
||||
input.set_enabled(bindname: str, flag: bool)
|
||||
```
|
||||
|
||||
Включает/выключает привязку до выхода из мира.
|
||||
|
||||
```python
|
||||
input.is_pressed(code: str) -> bool
|
||||
```
|
||||
|
||||
Проверяет активность ввода по коду, состоящему из:
|
||||
- типа ввода: key (клавиша) или mouse (кнопка мыши)
|
||||
- код ввода: [имя клавиши](#имена-клавиш) или имя кнопки мыши (left, middle, right)
|
||||
|
||||
Пример:
|
||||
```lua
|
||||
if input.is_pressed("key:enter") then
|
||||
...
|
||||
end
|
||||
```
|
||||
См. [библиотека *input*](builtins/libinput.md)
|
||||
|
||||
@ -48,6 +48,7 @@
|
||||
- `gravity` - автоматическое позиционирование элемента в контейнере. (Не работает в автоматических контейнерах, как panel). Значения: *top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right*.
|
||||
- `z-index` - определяет порядок элементов, при большем значении будет перекрывать элементы с меньшим.
|
||||
- `interactive` - при значении false наведение на элемент и все под-элементы будет игнорироваться.
|
||||
- `cursor` - курсор, отображаемый при наведении на элемент (arrow/text/pointer/crosshair/ew-resize/ns-resize/...).
|
||||
|
||||
# Атрибуты шаблонов
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"texture": "bazalt",
|
||||
"breakable": false
|
||||
"breakable": false,
|
||||
"base:durability": 1e9
|
||||
}
|
||||
|
||||
@ -9,5 +9,6 @@
|
||||
"grounded": true,
|
||||
"model": "X",
|
||||
"hitbox": [0.15, 0.0, 0.15, 0.7, 0.7, 0.7],
|
||||
"base:durability": 0.0
|
||||
"base:durability": 0.0,
|
||||
"base:loot": []
|
||||
}
|
||||
|
||||
@ -8,5 +8,8 @@
|
||||
"grass_side",
|
||||
"grass_side"
|
||||
],
|
||||
"base:durability": 1.3
|
||||
"base:durability": 1.3,
|
||||
"base:loot": [
|
||||
{"item": "base:dirt.item"}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1 +1,2 @@
|
||||
"base:durability" = {}
|
||||
"base:loot" = {}
|
||||
|
||||
@ -11,4 +11,34 @@ function util.drop(ppos, itemid, count, pickup_delay)
|
||||
}})
|
||||
end
|
||||
|
||||
local function calc_loot(loot_table)
|
||||
local results = {}
|
||||
for _, loot in ipairs(loot_table) do
|
||||
local chance = loot.chance or 1
|
||||
local count = loot.count or 1
|
||||
|
||||
local roll = math.random()
|
||||
|
||||
if roll < chance then
|
||||
if loot.min and loot.max then
|
||||
count = math.random(loot.min, loot.max)
|
||||
end
|
||||
if count == 0 then
|
||||
goto continue
|
||||
end
|
||||
table.insert(results, {item=item.index(loot.item), count=count})
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
function util.block_loot(blockid)
|
||||
local lootscheme = block.properties[blockid]["base:loot"]
|
||||
if lootscheme then
|
||||
return calc_loot(lootscheme)
|
||||
end
|
||||
return {{item=block.get_picking_item(blockid), count=1}}
|
||||
end
|
||||
|
||||
return util
|
||||
|
||||
@ -3,17 +3,32 @@ local body = entity.rigidbody
|
||||
local rig = entity.skeleton
|
||||
|
||||
local blockid = ARGS.block
|
||||
local blockstates = ARGS.states or 0
|
||||
if SAVED_DATA.block then
|
||||
blockid = SAVED_DATA.block
|
||||
blockstates = SAVED_DATA.states or 0
|
||||
else
|
||||
SAVED_DATA.block = blockid
|
||||
SAVED_DATA.states = blockstates
|
||||
end
|
||||
|
||||
do -- setup visuals
|
||||
local textures = block.get_textures(block.index(blockid))
|
||||
local id = block.index(blockid)
|
||||
local rotation = block.decompose_state(blockstates)[1]
|
||||
local textures = block.get_textures(id)
|
||||
for i,t in ipairs(textures) do
|
||||
rig:set_texture("$"..tostring(i-1), "blocks:"..textures[i])
|
||||
end
|
||||
local axisX = {block.get_X(id, rotation)}
|
||||
local axisY = {block.get_Y(id, rotation)}
|
||||
local axisZ = {block.get_Z(id, rotation)}
|
||||
local matrix = {
|
||||
axisX[1], axisX[2], axisX[3], 0,
|
||||
axisY[1], axisY[2], axisY[3], 0,
|
||||
axisZ[1], axisZ[2], axisZ[3], 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
rig:set_matrix(0, matrix)
|
||||
end
|
||||
|
||||
function on_grounded()
|
||||
@ -22,7 +37,7 @@ function on_grounded()
|
||||
local iy = math.floor(pos[2])
|
||||
local iz = math.floor(pos[3])
|
||||
if block.is_replaceable_at(ix, iy, iz) then
|
||||
block.place(ix, iy, iz, block.index(blockid), 0)
|
||||
block.place(ix, iy, iz, block.index(blockid), blockstates)
|
||||
else
|
||||
local picking_item = block.get_picking_item(block.index(blockid))
|
||||
local drop = entities.spawn("base:drop", pos, {base__drop={id=picking_item, count=1}})
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
function on_block_broken(id, x, y, z, playerid)
|
||||
gfx.particles.emit({x+0.5, y+0.5, z+0.5}, 64, {
|
||||
lifetime=1.0,
|
||||
spawn_interval=0.0001,
|
||||
explosion={4, 4, 4},
|
||||
texture="blocks:"..block.get_textures(id)[1],
|
||||
random_sub_uv=0.1,
|
||||
size={0.1, 0.1, 0.1},
|
||||
spawn_shape="box",
|
||||
spawn_spread={0.4, 0.4, 0.4}
|
||||
})
|
||||
if gfx then
|
||||
gfx.particles.emit({x+0.5, y+0.5, z+0.5}, 64, {
|
||||
lifetime=1.0,
|
||||
spawn_interval=0.0001,
|
||||
explosion={4, 4, 4},
|
||||
texture="blocks:"..block.get_textures(id)[1],
|
||||
random_sub_uv=0.1,
|
||||
size={0.1, 0.1, 0.1},
|
||||
spawn_shape="box",
|
||||
spawn_spread={0.4, 0.4, 0.4}
|
||||
})
|
||||
end
|
||||
|
||||
rules.create("do-loot-non-player", true)
|
||||
end
|
||||
|
||||
@ -99,8 +99,14 @@ function refresh()
|
||||
end
|
||||
end
|
||||
|
||||
local packids = {unpack(packs_installed)}
|
||||
for i,k in ipairs(packs_available) do
|
||||
table.insert(packids, k)
|
||||
end
|
||||
local packinfos = pack.get_info(packids)
|
||||
|
||||
for i,id in ipairs(packs_installed) do
|
||||
local packinfo = pack.get_info(id)
|
||||
local packinfo = packinfos[id]
|
||||
packinfo.index = i
|
||||
callback = not table.has(base_packs, id) and string.format('move_pack("%s")', id) or nil
|
||||
packinfo.error = check_dependencies(packinfo)
|
||||
@ -108,7 +114,7 @@ function refresh()
|
||||
end
|
||||
|
||||
for i,id in ipairs(packs_available) do
|
||||
local packinfo = pack.get_info(id)
|
||||
local packinfo = packinfos[id]
|
||||
packinfo.index = i
|
||||
callback = string.format('move_pack("%s")', id)
|
||||
packinfo.error = check_dependencies(packinfo)
|
||||
|
||||
@ -247,8 +247,9 @@ function refresh()
|
||||
local contents = document.contents
|
||||
contents:clear()
|
||||
|
||||
local packinfos = pack.get_info(packs_installed)
|
||||
for i, id in ipairs(packs_installed) do
|
||||
local packinfo = pack.get_info(id)
|
||||
local packinfo = packinfos[id]
|
||||
|
||||
packinfo.id = id
|
||||
packs_installed[i] = {packinfo.id, packinfo.title}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<panel size='400' color='0' interval='1' context='menu'>
|
||||
<button onclick='menu.page="worlds"'>@Worlds</button>
|
||||
<button onclick='menu.page="scripts"'>@Scripts</button>
|
||||
<button onclick='menu.page="settings"'>@Settings</button>
|
||||
<button onclick='menu.page="content_menu"'>@Contents Menu</button>
|
||||
<button onclick='core.quit()'>@Quit</button>
|
||||
|
||||
12
res/layouts/pages/scripts.xml
Normal file
12
res/layouts/pages/scripts.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<panel size='400' color='#00000030' context='menu'>
|
||||
<container size='400,32'>
|
||||
<label pos='2,10'>@Scripts</label>
|
||||
<image onclick='refresh()' interactive='true' src='gui/refresh'
|
||||
size='32' gravity='top-right'
|
||||
color='#FFFFFF80' hover-color='#FFFFFF10'/>
|
||||
</container>
|
||||
<panel id="list" size='400' interval='1' color='0' max-length='300'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
<button onclick='menu:back()'>@Back</button>
|
||||
</panel>
|
||||
40
res/layouts/pages/scripts.xml.lua
Normal file
40
res/layouts/pages/scripts.xml.lua
Normal file
@ -0,0 +1,40 @@
|
||||
function run_script(path)
|
||||
debug.log("starting application script "..path)
|
||||
|
||||
local code = file.read(path)
|
||||
local chunk, err = loadstring(code, path)
|
||||
if chunk == nil then
|
||||
error(err)
|
||||
end
|
||||
setfenv(chunk, setmetatable({app=__vc_app}, {__index=_G}))
|
||||
start_coroutine(chunk, path)
|
||||
end
|
||||
|
||||
function refresh()
|
||||
document.list:clear()
|
||||
|
||||
local available = pack.get_available()
|
||||
local infos = pack.get_info(available)
|
||||
for _, name in ipairs(available) do
|
||||
local info = infos[name]
|
||||
local scripts_dir = info.path.."/scripts/app"
|
||||
if not file.exists(scripts_dir) then
|
||||
goto continue
|
||||
end
|
||||
local files = file.list(scripts_dir)
|
||||
for _, filename in ipairs(files) do
|
||||
if file.ext(filename) == "lua" then
|
||||
document.list:add(gui.template("script", {
|
||||
pack=name,
|
||||
name=file.stem(filename),
|
||||
path=filename
|
||||
}))
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
function on_open()
|
||||
refresh()
|
||||
end
|
||||
3
res/layouts/templates/script.xml
Normal file
3
res/layouts/templates/script.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<button color='#10305080' hover-color='#10305040' onclick='run_script("%{path}")'>
|
||||
%{name} [%{pack}]
|
||||
</button>
|
||||
@ -13,15 +13,15 @@ local MAX_INT64 = 9223372036854775807
|
||||
local MIN_INT64 = -9223372036854775808
|
||||
|
||||
local function maskHighBytes(num)
|
||||
return bit.band(num, 0xFF)
|
||||
return bit.band(num, 0xFF)
|
||||
end
|
||||
|
||||
local function reverse(tbl)
|
||||
for i=1, math.floor(#tbl / 2) do
|
||||
local tmp = tbl[i]
|
||||
tbl[i] = tbl[#tbl - i + 1]
|
||||
tbl[#tbl - i + 1] = tmp
|
||||
end
|
||||
for i=1, math.floor(#tbl / 2) do
|
||||
local tmp = tbl[i]
|
||||
tbl[i] = tbl[#tbl - i + 1]
|
||||
tbl[#tbl - i + 1] = tmp
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
@ -29,60 +29,60 @@ local orders = { "LE", "BE" }
|
||||
|
||||
local fromLEConvertors =
|
||||
{
|
||||
LE = function(bytes) return bytes end,
|
||||
BE = function(bytes) return reverse(bytes) end
|
||||
LE = function(bytes) return bytes end,
|
||||
BE = function(bytes) return reverse(bytes) end
|
||||
}
|
||||
|
||||
local toLEConvertors =
|
||||
{
|
||||
LE = function(bytes) return bytes end,
|
||||
BE = function(bytes) return reverse(bytes) end
|
||||
LE = function(bytes) return bytes end,
|
||||
BE = function(bytes) return reverse(bytes) end
|
||||
}
|
||||
|
||||
bit_converter.default_order = "LE"
|
||||
bit_converter.default_order = "BE"
|
||||
|
||||
local function fromLE(bytes, orderTo)
|
||||
if orderTo then
|
||||
bit_converter.validate_order(orderTo)
|
||||
return fromLEConvertors[orderTo](bytes)
|
||||
else return bytes end
|
||||
if orderTo then
|
||||
bit_converter.validate_order(orderTo)
|
||||
return fromLEConvertors[orderTo](bytes)
|
||||
else return bytes end
|
||||
end
|
||||
|
||||
local function toLE(bytes, orderFrom)
|
||||
if orderFrom then
|
||||
bit_converter.validate_order(orderFrom)
|
||||
return toLEConvertors[orderFrom](bytes)
|
||||
else return bytes end
|
||||
if orderFrom then
|
||||
bit_converter.validate_order(orderFrom)
|
||||
return toLEConvertors[orderFrom](bytes)
|
||||
else return bytes end
|
||||
end
|
||||
|
||||
function bit_converter.validate_order(order)
|
||||
if not bit_converter.is_valid_order(order) then
|
||||
error("invalid order: "..order)
|
||||
end
|
||||
if not bit_converter.is_valid_order(order) then
|
||||
error("invalid order: "..order)
|
||||
end
|
||||
end
|
||||
|
||||
function bit_converter.is_valid_order(order) return table.has(orders, order) end
|
||||
|
||||
function bit_converter.string_to_bytes(str)
|
||||
local bytes = { }
|
||||
local bytes = { }
|
||||
|
||||
local len = string.len(str)
|
||||
local len = string.len(str)
|
||||
|
||||
local lenBytes = bit_converter.uint16_to_bytes(len)
|
||||
local lenBytes = bit_converter.uint16_to_bytes(len)
|
||||
|
||||
for i = 1, #lenBytes do
|
||||
bytes[i] = lenBytes[i]
|
||||
end
|
||||
for i = 1, #lenBytes do
|
||||
bytes[i] = lenBytes[i]
|
||||
end
|
||||
|
||||
for i = 1, len do
|
||||
bytes[#bytes + 1] = string.byte(string.sub(str, i, i))
|
||||
end
|
||||
for i = 1, len do
|
||||
bytes[#bytes + 1] = string.byte(string.sub(str, i, i))
|
||||
end
|
||||
|
||||
return bytes
|
||||
return bytes
|
||||
end
|
||||
|
||||
function bit_converter.bool_to_byte(bool)
|
||||
return bool and 1 or 0
|
||||
return bool and 1 or 0
|
||||
end
|
||||
|
||||
-- Credits to Iryont <https://github.com/iryont/lua-struct>
|
||||
@ -151,177 +151,178 @@ end
|
||||
--
|
||||
|
||||
function bit_converter.float32_to_bytes(float, order)
|
||||
return fromLE(floatOrDoubleToBytes(float, 'f'), order)
|
||||
return fromLE(floatOrDoubleToBytes(float, 'f'), order)
|
||||
end
|
||||
|
||||
function bit_converter.float64_to_bytes(float, order)
|
||||
return fromLE(floatOrDoubleToBytes(float, 'd'), order)
|
||||
return fromLE(floatOrDoubleToBytes(float, 'd'), order)
|
||||
end
|
||||
|
||||
function bit_converter.single_to_bytes(float, order)
|
||||
on_deprecated_call("bit_converter.float_to_bytes", "bit_converter.float32_to_bytes")
|
||||
return bit_converter.float32_to_bytes(bytes, order)
|
||||
on_deprecated_call("bit_converter.float_to_bytes", "bit_converter.float32_to_bytes")
|
||||
return bit_converter.float32_to_bytes(bytes, order)
|
||||
end
|
||||
|
||||
function bit_converter.double_to_bytes(double, order)
|
||||
on_deprecated_call("bit_converter.double_to_bytes", "bit_converter.float64_to_bytes")
|
||||
return bit_converter.float64_to_bytes(bytes, order)
|
||||
on_deprecated_call("bit_converter.double_to_bytes", "bit_converter.float64_to_bytes")
|
||||
return bit_converter.float64_to_bytes(bytes, order)
|
||||
end
|
||||
|
||||
local function uint32ToBytes(int, order)
|
||||
return fromLE({
|
||||
maskHighBytes(bit.rshift(int, 24)),
|
||||
maskHighBytes(bit.rshift(int, 16)),
|
||||
maskHighBytes(bit.rshift(int, 8)),
|
||||
maskHighBytes(int)
|
||||
}, order)
|
||||
return fromLE({
|
||||
maskHighBytes(int),
|
||||
maskHighBytes(bit.rshift(int, 8)),
|
||||
maskHighBytes(bit.rshift(int, 16)),
|
||||
maskHighBytes(bit.rshift(int, 24))
|
||||
}, order)
|
||||
end
|
||||
|
||||
local function uint16ToBytes(int, order)
|
||||
return fromLE({
|
||||
maskHighBytes(bit.rshift(int, 8)),
|
||||
maskHighBytes(int)
|
||||
}, order)
|
||||
return fromLE({
|
||||
maskHighBytes(int),
|
||||
maskHighBytes(bit.rshift(int, 8))
|
||||
}, order)
|
||||
end
|
||||
|
||||
function bit_converter.uint32_to_bytes(int, order)
|
||||
if int > MAX_UINT32 or int < MIN_UINT32 then
|
||||
error("invalid uint32")
|
||||
end
|
||||
if int > MAX_UINT32 or int < MIN_UINT32 then
|
||||
error("invalid uint32")
|
||||
end
|
||||
|
||||
return uint32ToBytes(int, order)
|
||||
return uint32ToBytes(int, order)
|
||||
end
|
||||
|
||||
function bit_converter.uint16_to_bytes(int, order)
|
||||
if int > MAX_UINT16 or int < MIN_UINT16 then
|
||||
error("invalid uint16")
|
||||
end
|
||||
if int > MAX_UINT16 or int < MIN_UINT16 then
|
||||
error("invalid uint16")
|
||||
end
|
||||
|
||||
return uint16ToBytes(int, order)
|
||||
return uint16ToBytes(int, order)
|
||||
end
|
||||
|
||||
function bit_converter.int64_to_bytes(int, order)
|
||||
if int > MAX_INT64 or int < MIN_INT64 then
|
||||
error("invalid int64")
|
||||
end
|
||||
if int > MAX_INT64 or int < MIN_INT64 then
|
||||
error("invalid int64")
|
||||
end
|
||||
|
||||
return fromLE({
|
||||
maskHighBytes(bit.rshift(int, 56)),
|
||||
maskHighBytes(bit.rshift(int, 48)),
|
||||
maskHighBytes(bit.rshift(int, 40)),
|
||||
maskHighBytes(bit.rshift(int, 32)),
|
||||
maskHighBytes(bit.rshift(int, 24)),
|
||||
maskHighBytes(bit.rshift(int, 16)),
|
||||
maskHighBytes(bit.rshift(int, 8)),
|
||||
maskHighBytes(int)
|
||||
}, order)
|
||||
return fromLE({
|
||||
maskHighBytes(int),
|
||||
maskHighBytes(bit.rshift(int, 8)),
|
||||
maskHighBytes(bit.rshift(int, 16)),
|
||||
maskHighBytes(bit.rshift(int, 24)),
|
||||
maskHighBytes(bit.rshift(int, 32)),
|
||||
maskHighBytes(bit.rshift(int, 40)),
|
||||
maskHighBytes(bit.rshift(int, 48)),
|
||||
maskHighBytes(bit.rshift(int, 56))
|
||||
}, order)
|
||||
end
|
||||
|
||||
function bit_converter.int32_to_bytes(int, order)
|
||||
on_deprecated_call("bit_converter.int32_to_bytes", "bit_converter.sint32_to_bytes")
|
||||
on_deprecated_call("bit_converter.int32_to_bytes", "bit_converter.sint32_to_bytes")
|
||||
|
||||
if int > MAX_INT32 or int < MIN_INT32 then
|
||||
error("invalid int32")
|
||||
end
|
||||
if int > MAX_INT32 or int < MIN_INT32 then
|
||||
error("invalid int32")
|
||||
end
|
||||
|
||||
return uint32ToBytes(int + MAX_INT32, order)
|
||||
return uint32ToBytes(int + MAX_INT32, order)
|
||||
end
|
||||
|
||||
function bit_converter.int16_to_bytes(int, order)
|
||||
on_deprecated_call("bit_converter.int32_to_bytes", "bit_converter.sint16_to_bytes")
|
||||
on_deprecated_call("bit_converter.int32_to_bytes", "bit_converter.sint16_to_bytes")
|
||||
|
||||
if int > MAX_INT16 or int < MIN_INT16 then
|
||||
error("invalid int16")
|
||||
end
|
||||
if int > MAX_INT16 or int < MIN_INT16 then
|
||||
error("invalid int16")
|
||||
end
|
||||
|
||||
return uint16ToBytes(int + MAX_INT16, order)
|
||||
return uint16ToBytes(int + MAX_INT16, order)
|
||||
end
|
||||
|
||||
function bit_converter.sint32_to_bytes(int, order)
|
||||
if int > MAX_INT32 or int < MIN_INT32 then
|
||||
error("invalid sint32")
|
||||
end
|
||||
if int > MAX_INT32 or int < MIN_INT32 then
|
||||
error("invalid sint32")
|
||||
end
|
||||
|
||||
return uint32ToBytes(int + MAX_UINT32 + 1, order)
|
||||
return uint32ToBytes(int + MAX_UINT32 + 1, order)
|
||||
end
|
||||
|
||||
function bit_converter.sint16_to_bytes(int, order)
|
||||
if int > MAX_INT16 or int < MIN_INT16 then
|
||||
error("invalid sint16")
|
||||
end
|
||||
if int > MAX_INT16 or int < MIN_INT16 then
|
||||
error("invalid sint16")
|
||||
end
|
||||
|
||||
return uint16ToBytes(int + MAX_UINT16 + 1, order)
|
||||
return uint16ToBytes(int + MAX_UINT16 + 1, order)
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_float32(bytes, order)
|
||||
return bytesToFloatOrDouble(toLE(bytes, order), 'f')
|
||||
return bytesToFloatOrDouble(toLE(bytes, order), 'f')
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_float64(bytes, order)
|
||||
return bytesToFloatOrDouble(toLE(bytes, order), 'd')
|
||||
return bytesToFloatOrDouble(toLE(bytes, order), 'd')
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_single(bytes, order)
|
||||
on_deprecated_call("bit_converter.bytes_to_single", "bit_converter.bytes_to_float32")
|
||||
return bit_converter.bytes_to_float32(bytes, order)
|
||||
on_deprecated_call("bit_converter.bytes_to_single", "bit_converter.bytes_to_float32")
|
||||
return bit_converter.bytes_to_float32(bytes, order)
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_double(bytes, order)
|
||||
on_deprecated_call("bit_converter.bytes_to_double", "bit_converter.bytes_to_float64")
|
||||
return bit_converter.bytes_to_float64(bytes, order)
|
||||
on_deprecated_call("bit_converter.bytes_to_double", "bit_converter.bytes_to_float64")
|
||||
return bit_converter.bytes_to_float64(bytes, order)
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_string(bytes, order)
|
||||
local len = bit_converter.bytes_to_uint16({ bytes[1], bytes[2] })
|
||||
local len = bit_converter.bytes_to_uint16({ bytes[1], bytes[2] })
|
||||
|
||||
local str = ""
|
||||
local str = ""
|
||||
|
||||
for i = 1, len do
|
||||
str = str..string.char(bytes[i + 2])
|
||||
end
|
||||
for i = 1, len do
|
||||
str = str..string.char(bytes[i + 2])
|
||||
end
|
||||
|
||||
return str
|
||||
return str
|
||||
end
|
||||
|
||||
function bit_converter.byte_to_bool(byte)
|
||||
return byte ~= 0
|
||||
return byte ~= 0
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_uint32(bytes, order)
|
||||
if #bytes < 4 then
|
||||
error("eof")
|
||||
end
|
||||
if #bytes < 4 then
|
||||
error("eof")
|
||||
end
|
||||
|
||||
bytes = toLE(bytes, order)
|
||||
bytes = toLE(bytes, order)
|
||||
|
||||
return
|
||||
bit.bor(
|
||||
bit.bor(
|
||||
bit.bor(
|
||||
bit.lshift(bytes[1], 24),
|
||||
bit.lshift(bytes[2], 16)),
|
||||
bit.lshift(bytes[3], 8)),bytes[4])
|
||||
bytes[1],
|
||||
bit.lshift(bytes[2], 8)),
|
||||
bit.lshift(bytes[3], 16)),
|
||||
bit.lshift(bytes[4], 24))
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_uint16(bytes, order)
|
||||
if #bytes < 2 then
|
||||
error("eof")
|
||||
end
|
||||
if #bytes < 2 then
|
||||
error("eof")
|
||||
end
|
||||
|
||||
bytes = toLE(bytes, order)
|
||||
bytes = toLE(bytes, order)
|
||||
|
||||
return
|
||||
bit.bor(
|
||||
bit.lshift(bytes[1], 8),
|
||||
bytes[2], 0)
|
||||
bit.lshift(bytes[2], 8),
|
||||
bytes[1], 0)
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_int64(bytes, order)
|
||||
if #bytes < 8 then
|
||||
error("eof")
|
||||
end
|
||||
if #bytes < 8 then
|
||||
error("eof")
|
||||
end
|
||||
|
||||
bytes = toLE(bytes, order)
|
||||
bytes = toLE(bytes, order)
|
||||
|
||||
return
|
||||
bit.bor(
|
||||
@ -331,35 +332,35 @@ function bit_converter.bytes_to_int64(bytes, order)
|
||||
bit.bor(
|
||||
bit.bor(
|
||||
bit.bor(
|
||||
bit.lshift(bytes[1], 56),
|
||||
bit.lshift(bytes[2], 48)),
|
||||
bit.lshift(bytes[3], 40)),
|
||||
bit.lshift(bytes[4], 32)),
|
||||
bit.lshift(bytes[5], 24)),
|
||||
bit.lshift(bit.band(bytes[6], 0xFF), 16)),
|
||||
bit.lshift(bit.band(bytes[7], 0xFF), 8)),bit.band(bytes[8], 0xFF))
|
||||
bit.lshift(bytes[8], 56),
|
||||
bit.lshift(bytes[7], 48)),
|
||||
bit.lshift(bytes[6], 40)),
|
||||
bit.lshift(bytes[5], 32)),
|
||||
bit.lshift(bytes[4], 24)),
|
||||
bit.lshift(bit.band(bytes[3], 0xFF), 16)),
|
||||
bit.lshift(bit.band(bytes[2], 0xFF), 8)),bit.band(bytes[1], 0xFF))
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_int32(bytes, order)
|
||||
on_deprecated_call("bit_converter.bytes_to_int32", "bit_converter.bytes_to_sint32")
|
||||
return bit_converter.bytes_to_uint32(bytes, order) - MAX_INT32
|
||||
on_deprecated_call("bit_converter.bytes_to_int32", "bit_converter.bytes_to_sint32")
|
||||
return bit_converter.bytes_to_uint32(bytes, order) - MAX_INT32
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_int16(bytes, order)
|
||||
on_deprecated_call("bit_converter.bytes_to_int16", "bit_converter.bytes_to_sint16")
|
||||
return bit_converter.bytes_to_uint16(bytes, order) - MAX_INT16
|
||||
on_deprecated_call("bit_converter.bytes_to_int16", "bit_converter.bytes_to_sint16")
|
||||
return bit_converter.bytes_to_uint16(bytes, order) - MAX_INT16
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_sint32(bytes, order)
|
||||
local num = bit_converter.bytes_to_uint32(bytes, order)
|
||||
local num = bit_converter.bytes_to_uint32(bytes, order)
|
||||
|
||||
return MIN_INT32 * (bit.band(MAX_INT32 + 1, num) ~= 0 and 1 or 0) + bit.band(MAX_INT32, num)
|
||||
return MIN_INT32 * (bit.band(MAX_INT32 + 1, num) ~= 0 and 1 or 0) + bit.band(MAX_INT32, num)
|
||||
end
|
||||
|
||||
function bit_converter.bytes_to_sint16(bytes, order)
|
||||
local num = bit_converter.bytes_to_uint16(bytes, order)
|
||||
local num = bit_converter.bytes_to_uint16(bytes, order)
|
||||
|
||||
return MIN_INT16 * (bit.band(MAX_INT16 + 1, num) ~= 0 and 1 or 0) + bit.band(MAX_INT16, num)
|
||||
return MIN_INT16 * (bit.band(MAX_INT16 + 1, num) ~= 0 and 1 or 0) + bit.band(MAX_INT16, num)
|
||||
end
|
||||
|
||||
return bit_converter
|
||||
|
||||
36
res/modules/gui_util.lua
Normal file
36
res/modules/gui_util.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local gui_util = {}
|
||||
|
||||
--- Parse `pagename?arg1=value1&arg2=value2` queries
|
||||
--- @param query page query string
|
||||
--- @return page_name, args_table
|
||||
function gui_util.parse_query(query)
|
||||
local args = {}
|
||||
local name
|
||||
|
||||
local index = string.find(query, '?')
|
||||
if index then
|
||||
local argstr = string.sub(query, index + 1)
|
||||
name = string.sub(query, 1, index - 1)
|
||||
|
||||
for key, value in string.gmatch(argstr, "([^=&]*)=([^&]*)") do
|
||||
args[key] = value
|
||||
end
|
||||
else
|
||||
name = query
|
||||
end
|
||||
return name, args
|
||||
end
|
||||
|
||||
--- @param query page query string
|
||||
--- @return document_id
|
||||
function gui_util.load_page(query)
|
||||
local name, args = gui_util.parse_query(query)
|
||||
local filename = file.find(string.format("layouts/pages/%s.xml", name))
|
||||
if filename then
|
||||
name = file.prefix(filename)..":pages/"..name
|
||||
gui.load_document(filename, name, args)
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
return gui_util
|
||||
8
res/modules/tests_util.lua
Normal file
8
res/modules/tests_util.lua
Normal file
@ -0,0 +1,8 @@
|
||||
local util = {}
|
||||
|
||||
function util.create_demo_world(generator)
|
||||
app.config_packs({"base"})
|
||||
app.new_world("demo", "2019", generator or "core:default")
|
||||
end
|
||||
|
||||
return util
|
||||
@ -31,5 +31,8 @@
|
||||
"blocks",
|
||||
"items",
|
||||
"particles"
|
||||
],
|
||||
"models": [
|
||||
"block"
|
||||
]
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ local Socket = {__index={
|
||||
send=function(self, ...) return network.__send(self.id, ...) end,
|
||||
recv=function(self, ...) return network.__recv(self.id, ...) end,
|
||||
close=function(self) return network.__close(self.id) end,
|
||||
available=function(self) return network.__available(self.id) or 0 end,
|
||||
is_alive=function(self) return network.__is_alive(self.id) end,
|
||||
is_connected=function(self) return network.__is_connected(self.id) end,
|
||||
get_address=function(self) return network.__get_address(self.id) end,
|
||||
|
||||
58
res/scripts/hud.lua
Normal file
58
res/scripts/hud.lua
Normal file
@ -0,0 +1,58 @@
|
||||
function on_hud_open()
|
||||
input.add_callback("player.pick", function ()
|
||||
if hud.is_paused() or hud.is_inventory_open() then
|
||||
return
|
||||
end
|
||||
local pid = hud.get_player()
|
||||
local x, y, z = player.get_selected_block(pid)
|
||||
if x == nil then
|
||||
return
|
||||
end
|
||||
local id = block.get_picking_item(block.get(x, y, z))
|
||||
local inv, cur_slot = player.get_inventory(pid)
|
||||
local slot = inventory.find_by_item(inv, id, 0, 9)
|
||||
if slot then
|
||||
player.set_selected_slot(pid, slot)
|
||||
return
|
||||
end
|
||||
if not rules.get("allow-content-access") then
|
||||
return
|
||||
end
|
||||
slot = inventory.find_by_item(inv, 0, 0, 9)
|
||||
if slot then
|
||||
cur_slot = slot
|
||||
end
|
||||
player.set_selected_slot(pid, cur_slot)
|
||||
inventory.set(inv, cur_slot, id, 1)
|
||||
end)
|
||||
|
||||
input.add_callback("player.noclip", function ()
|
||||
if hud.is_paused() or hud.is_inventory_open() then
|
||||
return
|
||||
end
|
||||
local pid = hud.get_player()
|
||||
if player.is_noclip(pid) then
|
||||
player.set_flight(pid, false)
|
||||
player.set_noclip(pid, false)
|
||||
else
|
||||
player.set_flight(pid, true)
|
||||
player.set_noclip(pid, true)
|
||||
end
|
||||
end)
|
||||
|
||||
input.add_callback("player.flight", function ()
|
||||
if hud.is_paused() or hud.is_inventory_open() then
|
||||
return
|
||||
end
|
||||
local pid = hud.get_player()
|
||||
if player.is_noclip(pid) then
|
||||
return
|
||||
end
|
||||
if player.is_flight(pid) then
|
||||
player.set_flight(pid, false)
|
||||
else
|
||||
player.set_flight(pid, true)
|
||||
player.set_vel(pid, 0, 1, 0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
@ -7,7 +7,7 @@ local names = {
|
||||
"hidden", "draw-group", "picking-item", "surface-replacement", "script-name",
|
||||
"ui-layout", "inventory-size", "tick-interval", "overlay-texture",
|
||||
"translucent", "fields", "particles", "icon-type", "icon", "placing-block",
|
||||
"stack-size"
|
||||
"stack-size", "name"
|
||||
}
|
||||
for name, _ in pairs(user_props) do
|
||||
table.insert(names, name)
|
||||
@ -40,3 +40,24 @@ make_read_only(block.properties)
|
||||
for k,v in pairs(block.properties) do
|
||||
make_read_only(v)
|
||||
end
|
||||
|
||||
local function cache_names(library)
|
||||
local indices = {}
|
||||
local names = {}
|
||||
for id=0,library.defs_count()-1 do
|
||||
local name = library.properties[id].name
|
||||
indices[name] = id
|
||||
names[id] = name
|
||||
end
|
||||
|
||||
function library.name(id)
|
||||
return names[id]
|
||||
end
|
||||
|
||||
function library.index(name)
|
||||
return indices[name]
|
||||
end
|
||||
end
|
||||
|
||||
cache_names(block)
|
||||
cache_names(item)
|
||||
|
||||
@ -9,6 +9,87 @@ function sleep(timesec)
|
||||
end
|
||||
end
|
||||
|
||||
function tb_frame_tostring(frame)
|
||||
local s = frame.short_src
|
||||
if frame.what ~= "C" then
|
||||
s = s .. ":" .. tostring(frame.currentline)
|
||||
end
|
||||
if frame.what == "main" then
|
||||
s = s .. ": in main chunk"
|
||||
elseif frame.name then
|
||||
s = s .. ": in function " .. utf8.escape(frame.name)
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function complete_app_lib(app)
|
||||
app.sleep = sleep
|
||||
app.script = __VC_SCRIPT_NAME
|
||||
app.new_world = core.new_world
|
||||
app.open_world = core.open_world
|
||||
app.save_world = core.save_world
|
||||
app.close_world = core.close_world
|
||||
app.reopen_world = core.reopen_world
|
||||
app.delete_world = core.delete_world
|
||||
app.reconfig_packs = core.reconfig_packs
|
||||
app.get_setting = core.get_setting
|
||||
app.set_setting = core.set_setting
|
||||
app.tick = coroutine.yield
|
||||
app.get_version = core.get_version
|
||||
app.get_setting_info = core.get_setting_info
|
||||
app.load_content = core.load_content
|
||||
app.reset_content = core.reset_content
|
||||
|
||||
function app.config_packs(packs_list)
|
||||
-- Check if packs are valid and add dependencies to the configuration
|
||||
packs_list = pack.assemble(packs_list)
|
||||
|
||||
local installed = pack.get_installed()
|
||||
local toremove = {}
|
||||
for _, packid in ipairs(installed) do
|
||||
if not table.has(packs_list, packid) then
|
||||
table.insert(toremove, packid)
|
||||
end
|
||||
end
|
||||
local toadd = {}
|
||||
for _, packid in ipairs(packs_list) do
|
||||
if not table.has(installed, packid) then
|
||||
table.insert(toadd, packid)
|
||||
end
|
||||
end
|
||||
app.reconfig_packs(toadd, toremove)
|
||||
end
|
||||
|
||||
function app.quit()
|
||||
local tb = debug.get_traceback(1)
|
||||
local s = "app.quit() traceback:"
|
||||
for i, frame in ipairs(tb) do
|
||||
s = s .. "\n\t"..tb_frame_tostring(frame)
|
||||
end
|
||||
debug.log(s)
|
||||
core.quit()
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
function app.sleep_until(predicate, max_ticks)
|
||||
max_ticks = max_ticks or 1e9
|
||||
local ticks = 0
|
||||
while ticks < max_ticks and not predicate() do
|
||||
app.tick()
|
||||
ticks = ticks + 1
|
||||
end
|
||||
if ticks == max_ticks then
|
||||
error("max ticks exceed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if app then
|
||||
complete_app_lib(app)
|
||||
elseif __vc_app then
|
||||
complete_app_lib(__vc_app)
|
||||
end
|
||||
|
||||
------------------------------------------------
|
||||
------------------- Events ---------------------
|
||||
------------------------------------------------
|
||||
@ -54,7 +135,12 @@ function events.emit(event, ...)
|
||||
return nil
|
||||
end
|
||||
for _, func in ipairs(handlers) do
|
||||
result = result or func(...)
|
||||
local status, newres = xpcall(func, __vc__error, ...)
|
||||
if not status then
|
||||
debug.error("error in event ("..event..") handler: "..newres)
|
||||
else
|
||||
result = result or newres
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
@ -85,7 +171,7 @@ function Document.new(docname)
|
||||
end
|
||||
|
||||
local _RadioGroup = {}
|
||||
function _RadioGroup.set(self, key)
|
||||
function _RadioGroup:set(key)
|
||||
if type(self) ~= 'table' then
|
||||
error("called as non-OOP via '.', use radiogroup:set")
|
||||
end
|
||||
@ -98,7 +184,7 @@ function _RadioGroup.set(self, key)
|
||||
self.callback(key)
|
||||
end
|
||||
end
|
||||
function _RadioGroup.__call(self, elements, onset, default)
|
||||
function _RadioGroup:__call(elements, onset, default)
|
||||
local group = setmetatable({
|
||||
elements=elements,
|
||||
callback=onset,
|
||||
@ -114,20 +200,8 @@ _GUI_ROOT = Document.new("core:root")
|
||||
_MENU = _GUI_ROOT.menu
|
||||
menu = _MENU
|
||||
|
||||
local __post_runnables = {}
|
||||
|
||||
function __process_post_runnables()
|
||||
if #__post_runnables then
|
||||
for _, func in ipairs(__post_runnables) do
|
||||
func()
|
||||
end
|
||||
__post_runnables = {}
|
||||
end
|
||||
end
|
||||
|
||||
function time.post_runnable(runnable)
|
||||
table.insert(__post_runnables, runnable)
|
||||
end
|
||||
local gui_util = require "core:gui_util"
|
||||
__vc_page_loader = gui_util.load_page
|
||||
|
||||
--- Console library extension ---
|
||||
console.cheats = {}
|
||||
@ -272,7 +346,6 @@ function __vc_on_hud_open()
|
||||
|
||||
_rules.create("allow-content-access", hud._is_content_access(), function(value)
|
||||
hud._set_content_access(value)
|
||||
input.set_enabled("player.pick", value)
|
||||
end)
|
||||
_rules.create("allow-flight", true, function(value)
|
||||
input.set_enabled("player.flight", value)
|
||||
@ -328,6 +401,86 @@ function __vc_on_world_quit()
|
||||
_rules.clear()
|
||||
end
|
||||
|
||||
local __vc_coroutines = {}
|
||||
local __vc_named_coroutines = {}
|
||||
local __vc_next_coroutine = 1
|
||||
local __vc_coroutine_error = nil
|
||||
|
||||
function __vc_start_coroutine(chunk)
|
||||
local co = coroutine.create(function()
|
||||
local status, err = pcall(chunk)
|
||||
if not status then
|
||||
__vc_coroutine_error = err
|
||||
end
|
||||
end)
|
||||
local id = __vc_next_coroutine
|
||||
__vc_next_coroutine = __vc_next_coroutine + 1
|
||||
__vc_coroutines[id] = co
|
||||
return id
|
||||
end
|
||||
|
||||
function __vc_resume_coroutine(id)
|
||||
local co = __vc_coroutines[id]
|
||||
if co then
|
||||
coroutine.resume(co)
|
||||
if __vc_coroutine_error then
|
||||
debug.error(__vc_coroutine_error)
|
||||
error(__vc_coroutine_error)
|
||||
end
|
||||
return coroutine.status(co) ~= "dead"
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function __vc_stop_coroutine(id)
|
||||
local co = __vc_coroutines[id]
|
||||
if co then
|
||||
if coroutine.close then
|
||||
coroutine.close(co)
|
||||
end
|
||||
__vc_coroutines[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function start_coroutine(chunk, name)
|
||||
local co = coroutine.create(function()
|
||||
local status, error = xpcall(chunk, __vc__error)
|
||||
if not status then
|
||||
debug.error(error)
|
||||
end
|
||||
end)
|
||||
__vc_named_coroutines[name] = co
|
||||
end
|
||||
|
||||
local __post_runnables = {}
|
||||
|
||||
function __process_post_runnables()
|
||||
if #__post_runnables then
|
||||
for _, func in ipairs(__post_runnables) do
|
||||
local status, result = xpcall(func, __vc__error)
|
||||
if not status then
|
||||
debug.error("error in post_runnable: "..result)
|
||||
end
|
||||
end
|
||||
__post_runnables = {}
|
||||
end
|
||||
|
||||
local dead = {}
|
||||
for name, co in pairs(__vc_named_coroutines) do
|
||||
coroutine.resume(co)
|
||||
if coroutine.status(co) == "dead" then
|
||||
table.insert(dead, name)
|
||||
end
|
||||
end
|
||||
for _, name in ipairs(dead) do
|
||||
__vc_named_coroutines[name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function time.post_runnable(runnable)
|
||||
table.insert(__post_runnables, runnable)
|
||||
end
|
||||
|
||||
assets = {}
|
||||
assets.load_texture = core.__load_texture
|
||||
|
||||
|
||||
@ -34,11 +34,11 @@ end
|
||||
|
||||
|
||||
function timeit(iters, func, ...)
|
||||
local tm = time.uptime()
|
||||
local tm = os.clock()
|
||||
for i=1,iters do
|
||||
func(...)
|
||||
end
|
||||
print("[time mcs]", (time.uptime()-tm) * 1000000)
|
||||
print("[time mcs]", (os.clock()-tm) * 1000000)
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
@ -361,3 +361,20 @@ function __vc_warning(msg, detail, n)
|
||||
"core:warning", msg, detail, debug.get_traceback(1 + (n or 0)))
|
||||
end
|
||||
end
|
||||
|
||||
function file.name(path)
|
||||
return path:match("([^:/\\]+)$")
|
||||
end
|
||||
|
||||
function file.stem(path)
|
||||
local name = file.name(path)
|
||||
return name:match("(.+)%.[^%.]+$") or name
|
||||
end
|
||||
|
||||
function file.ext(path)
|
||||
return path:match("%.([^:/\\]+)$")
|
||||
end
|
||||
|
||||
function file.prefix(path)
|
||||
return path:match("^([^:]+)")
|
||||
end
|
||||
|
||||
@ -34,6 +34,7 @@ graphics.dense-render.tooltip=Включает прозрачность блок
|
||||
menu.Apply=Применить
|
||||
menu.Audio=Звук
|
||||
menu.Back to Main Menu=Вернуться в Меню
|
||||
menu.Scripts=Сценарии
|
||||
menu.Content Error=Ошибка Контента
|
||||
menu.Content=Контент
|
||||
menu.Continue=Продолжить
|
||||
|
||||
@ -4,15 +4,19 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
file(GLOB_RECURSE HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp)
|
||||
file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/voxel_engine.cpp)
|
||||
list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
|
||||
|
||||
add_library(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
||||
|
||||
option(VOXELENGINE_BUILD_WINDOWS_VCPKG ON)
|
||||
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
find_package(OpenAL REQUIRED)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
# specific for vcpkg
|
||||
find_package(OpenAL CONFIG REQUIRED)
|
||||
set(OPENAL_LIBRARY OpenAL::OpenAL)
|
||||
else()
|
||||
find_package(OpenAL REQUIRED)
|
||||
endif()
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
@ -20,19 +24,23 @@ if (NOT APPLE)
|
||||
find_package(EnTT REQUIRED)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
if(VOXELENGINE_BUILD_WINDOWS_VCPKG)
|
||||
set(LUA_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/../vcpkg/packages/luajit_x64-windows/lib/lua51.lib")
|
||||
set(LUA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../vcpkg/packages/luajit_x64-windows/include/luajit")
|
||||
find_package(glfw3 REQUIRED)
|
||||
find_package(glm REQUIRED)
|
||||
find_package(vorbis REQUIRED)
|
||||
set(VORBISLIB Vorbis::vorbis Vorbis::vorbisfile)
|
||||
set(LIBS "")
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
# Use directly linking to lib instead PkgConfig (because pkg-config dont install on windows as default)
|
||||
# TODO: Do it with findLua.
|
||||
if (MSVC)
|
||||
set(LUA_INCLUDE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit")
|
||||
find_package(Lua REQUIRED)
|
||||
else()
|
||||
find_package(Lua REQUIRED)
|
||||
set(VORBISLIB vorbis vorbisfile) # not tested
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw)
|
||||
# Used for mingw-clang cross compiling from msys2
|
||||
set(LIBS ${LIBS} luajit-5.1)
|
||||
endif()
|
||||
find_package(glfw3 REQUIRED)
|
||||
find_package(glm REQUIRED)
|
||||
find_package(vorbis REQUIRED)
|
||||
set(VORBISLIB Vorbis::vorbis Vorbis::vorbisfile)
|
||||
|
||||
elseif(APPLE)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LUAJIT REQUIRED luajit)
|
||||
@ -53,8 +61,6 @@ else()
|
||||
set(VORBISLIB ${VORBIS_LDFLAGS})
|
||||
endif()
|
||||
|
||||
set(LIBS "")
|
||||
|
||||
if(UNIX)
|
||||
find_package(glfw3 3.3 REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
#include "coders/wav.hpp"
|
||||
#include "AL/ALAudio.hpp"
|
||||
#include "NoAudio.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
static debug::Logger logger("audio");
|
||||
|
||||
namespace audio {
|
||||
static speakerid_t nextId = 1;
|
||||
@ -147,10 +150,14 @@ public:
|
||||
|
||||
void audio::initialize(bool enabled) {
|
||||
if (enabled) {
|
||||
logger.info() << "initializing ALAudio backend";
|
||||
backend = ALAudio::create().release();
|
||||
}
|
||||
if (backend == nullptr) {
|
||||
std::cerr << "could not to initialize audio" << std::endl;
|
||||
if (enabled) {
|
||||
std::cerr << "could not to initialize audio" << std::endl;
|
||||
}
|
||||
logger.info() << "initializing NoAudio backend";
|
||||
backend = NoAudio::create().release();
|
||||
}
|
||||
create_channel("master");
|
||||
|
||||
@ -6,6 +6,10 @@
|
||||
|
||||
#include "util/data_io.hpp"
|
||||
|
||||
ByteBuilder::ByteBuilder(size_t size) {
|
||||
buffer.reserve(size);
|
||||
}
|
||||
|
||||
void ByteBuilder::put(ubyte b) {
|
||||
buffer.push_back(b);
|
||||
}
|
||||
@ -31,37 +35,37 @@ void ByteBuilder::put(const ubyte* arr, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void ByteBuilder::putInt16(int16_t val) {
|
||||
void ByteBuilder::putInt16(int16_t val, bool bigEndian) {
|
||||
size_t size = buffer.size();
|
||||
buffer.resize(buffer.size() + sizeof(int16_t));
|
||||
val = dataio::h2le(val);
|
||||
val = bigEndian ? dataio::h2be(val) : dataio::h2le(val);
|
||||
std::memcpy(buffer.data()+size, &val, sizeof(int16_t));
|
||||
}
|
||||
|
||||
void ByteBuilder::putInt32(int32_t val) {
|
||||
void ByteBuilder::putInt32(int32_t val, bool bigEndian) {
|
||||
size_t size = buffer.size();
|
||||
buffer.resize(buffer.size() + sizeof(int32_t));
|
||||
val = dataio::h2le(val);
|
||||
val = bigEndian ? dataio::h2be(val) : dataio::h2le(val);
|
||||
std::memcpy(buffer.data()+size, &val, sizeof(int32_t));
|
||||
}
|
||||
|
||||
void ByteBuilder::putInt64(int64_t val) {
|
||||
void ByteBuilder::putInt64(int64_t val, bool bigEndian) {
|
||||
size_t size = buffer.size();
|
||||
buffer.resize(buffer.size() + sizeof(int64_t));
|
||||
val = dataio::h2le(val);
|
||||
val = bigEndian ? dataio::h2be(val) : dataio::h2le(val);
|
||||
std::memcpy(buffer.data()+size, &val, sizeof(int64_t));
|
||||
}
|
||||
|
||||
void ByteBuilder::putFloat32(float val) {
|
||||
void ByteBuilder::putFloat32(float val, bool bigEndian) {
|
||||
int32_t i32_val;
|
||||
std::memcpy(&i32_val, &val, sizeof(int32_t));
|
||||
putInt32(i32_val);
|
||||
putInt32(i32_val, bigEndian);
|
||||
}
|
||||
|
||||
void ByteBuilder::putFloat64(double val) {
|
||||
void ByteBuilder::putFloat64(double val, bool bigEndian) {
|
||||
int64_t i64_val;
|
||||
std::memcpy(&i64_val, &val, sizeof(int64_t));
|
||||
putInt64(i64_val);
|
||||
putInt64(i64_val, bigEndian);
|
||||
}
|
||||
|
||||
void ByteBuilder::set(size_t position, ubyte val) {
|
||||
@ -95,6 +99,10 @@ ByteReader::ByteReader(const ubyte* data) : data(data), size(4), pos(0) {
|
||||
size = getInt32();
|
||||
}
|
||||
|
||||
ByteReader::ByteReader(const std::vector<ubyte>& data)
|
||||
: data(data.data()), size(data.size()), pos(0) {
|
||||
}
|
||||
|
||||
void ByteReader::checkMagic(const char* data, size_t size) {
|
||||
if (pos + size >= this->size) {
|
||||
throw std::runtime_error("invalid magic number");
|
||||
@ -129,45 +137,51 @@ ubyte ByteReader::peek() {
|
||||
return data[pos];
|
||||
}
|
||||
|
||||
int16_t ByteReader::getInt16() {
|
||||
int16_t ByteReader::getInt16(bool bigEndian) {
|
||||
if (pos + sizeof(int16_t) > size) {
|
||||
throw std::runtime_error("buffer underflow");
|
||||
}
|
||||
int16_t value;
|
||||
std::memcpy(&value, data + pos, sizeof(int16_t));
|
||||
pos += sizeof(int16_t);
|
||||
return dataio::le2h(value);
|
||||
return bigEndian ? dataio::be2h(value) : dataio::le2h(value);
|
||||
}
|
||||
|
||||
int32_t ByteReader::getInt32() {
|
||||
int32_t ByteReader::getInt32(bool bigEndian) {
|
||||
if (pos + sizeof(int32_t) > size) {
|
||||
throw std::runtime_error("buffer underflow");
|
||||
}
|
||||
int32_t value;
|
||||
std::memcpy(&value, data + pos, sizeof(int32_t));
|
||||
pos += sizeof(int32_t);
|
||||
return dataio::le2h(value);
|
||||
return bigEndian ? dataio::be2h(value) : dataio::le2h(value);
|
||||
}
|
||||
|
||||
int64_t ByteReader::getInt64() {
|
||||
int64_t ByteReader::getInt64(bool bigEndian) {
|
||||
if (pos + sizeof(int64_t) > size) {
|
||||
throw std::runtime_error("buffer underflow");
|
||||
}
|
||||
int64_t value;
|
||||
std::memcpy(&value, data + pos, sizeof(int64_t));
|
||||
pos += sizeof(int64_t);
|
||||
return dataio::le2h(value);
|
||||
return bigEndian ? dataio::be2h(value) : dataio::le2h(value);
|
||||
}
|
||||
|
||||
float ByteReader::getFloat32() {
|
||||
float ByteReader::getFloat32(bool bigEndian) {
|
||||
int32_t i32_val = getInt32();
|
||||
if (bigEndian) {
|
||||
i32_val = dataio::be2h(i32_val);
|
||||
}
|
||||
float val;
|
||||
std::memcpy(&val, &i32_val, sizeof(float));
|
||||
return val;
|
||||
}
|
||||
|
||||
double ByteReader::getFloat64() {
|
||||
double ByteReader::getFloat64(bool bigEndian) {
|
||||
int64_t i64_val = getInt64();
|
||||
if (bigEndian) {
|
||||
i64_val = dataio::be2h(i64_val);
|
||||
}
|
||||
double val;
|
||||
std::memcpy(&val, &i64_val, sizeof(double));
|
||||
return val;
|
||||
|
||||
@ -8,20 +8,23 @@
|
||||
class ByteBuilder {
|
||||
std::vector<ubyte> buffer;
|
||||
public:
|
||||
ByteBuilder() = default;
|
||||
ByteBuilder(size_t size);
|
||||
|
||||
/// @brief Write one byte (8 bit unsigned integer)
|
||||
void put(ubyte b);
|
||||
/// @brief Write c-string (bytes array terminated with '\00')
|
||||
void putCStr(const char* str);
|
||||
/// @brief Write signed 16 bit little-endian integer
|
||||
void putInt16(int16_t val);
|
||||
void putInt16(int16_t val, bool bigEndian = false);
|
||||
/// @brief Write signed 32 bit integer
|
||||
void putInt32(int32_t val);
|
||||
void putInt32(int32_t val, bool bigEndian = false);
|
||||
/// @brief Write signed 64 bit integer
|
||||
void putInt64(int64_t val);
|
||||
void putInt64(int64_t val, bool bigEndian = false);
|
||||
/// @brief Write 32 bit floating-point number
|
||||
void putFloat32(float val);
|
||||
void putFloat32(float val, bool bigEndian = false);
|
||||
/// @brief Write 64 bit floating-point number
|
||||
void putFloat64(double val);
|
||||
void putFloat64(double val, bool bigEndian = false);
|
||||
|
||||
/// @brief Write string (uint32 length + bytes)
|
||||
void put(const std::string& s);
|
||||
@ -50,6 +53,7 @@ class ByteReader {
|
||||
public:
|
||||
ByteReader(const ubyte* data, size_t size);
|
||||
ByteReader(const ubyte* data);
|
||||
ByteReader(const std::vector<ubyte>& data);
|
||||
|
||||
void checkMagic(const char* data, size_t size);
|
||||
/// @brief Get N bytes
|
||||
@ -59,15 +63,15 @@ public:
|
||||
/// @brief Read one byte (unsigned 8 bit integer) without pointer move
|
||||
ubyte peek();
|
||||
/// @brief Read signed 16 bit little-endian integer
|
||||
int16_t getInt16();
|
||||
int16_t getInt16(bool bigEndian = false);
|
||||
/// @brief Read signed 32 bit little-endian integer
|
||||
int32_t getInt32();
|
||||
int32_t getInt32(bool bigEndian = false);
|
||||
/// @brief Read signed 64 bit little-endian integer
|
||||
int64_t getInt64();
|
||||
int64_t getInt64(bool bigEndian = false);
|
||||
/// @brief Read 32 bit floating-point number
|
||||
float getFloat32();
|
||||
float getFloat32(bool bigEndian = false);
|
||||
/// @brief Read 64 bit floating-point number
|
||||
double getFloat64();
|
||||
double getFloat64(bool bigEndian = false);
|
||||
/// @brief Read C-String
|
||||
const char* getCString();
|
||||
/// @brief Read string with unsigned 32 bit number before (length)
|
||||
|
||||
@ -92,11 +92,11 @@ glm::vec4 Attribute::asColor() const {
|
||||
throw std::runtime_error("#RRGGBB or #RRGGBBAA required");
|
||||
}
|
||||
int a = 255;
|
||||
int r = (hexchar2int(text[1]) << 4) | hexchar2int(text[2]);
|
||||
int g = (hexchar2int(text[3]) << 4) | hexchar2int(text[4]);
|
||||
int b = (hexchar2int(text[5]) << 4) | hexchar2int(text[6]);
|
||||
int r = (std::max(0, hexchar2int(text[1])) << 4) | hexchar2int(text[2]);
|
||||
int g = (std::max(0, hexchar2int(text[3])) << 4) | hexchar2int(text[4]);
|
||||
int b = (std::max(0, hexchar2int(text[5])) << 4) | hexchar2int(text[6]);
|
||||
if (text.length() == 9) {
|
||||
a = (hexchar2int(text[7]) << 4) | hexchar2int(text[8]);
|
||||
a = (std::max(0, hexchar2int(text[7])) << 4) | hexchar2int(text[8]);
|
||||
}
|
||||
return glm::vec4(r / 255.f, g / 255.f, b / 255.f, a / 255.f);
|
||||
} else {
|
||||
|
||||
@ -35,6 +35,11 @@ inline constexpr int CHUNK_D = 16;
|
||||
inline constexpr uint VOXEL_USER_BITS = 8;
|
||||
inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS;
|
||||
|
||||
/// @brief % unordered map max average buckets load factor.
|
||||
/// Low value gives significant performance impact by minimizing collisions and
|
||||
/// lookup latency. Default value (1.0) shows x2 slower work.
|
||||
inline constexpr float CHUNKS_MAP_MAX_LOAD_FACTOR = 0.1f;
|
||||
|
||||
/// @brief chunk volume (count of voxels per Chunk)
|
||||
inline constexpr int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D);
|
||||
|
||||
|
||||
@ -91,10 +91,18 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
for (Block* def : blockDefsIndices) {
|
||||
def->rt.pickingItem = content->items.require(def->pickingItem).rt.id;
|
||||
def->rt.surfaceReplacement = content->blocks.require(def->surfaceReplacement).rt.id;
|
||||
if (def->properties == nullptr) {
|
||||
def->properties = dv::object();
|
||||
def->properties["name"] = def->name;
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemDef* def : itemDefsIndices) {
|
||||
def->rt.placingBlock = content->blocks.require(def->placingBlock).rt.id;
|
||||
if (def->properties == nullptr) {
|
||||
def->properties = dv::object();
|
||||
}
|
||||
def->properties["name"] = def->name;
|
||||
}
|
||||
|
||||
for (auto& [name, def] : content->generators.getDefs()) {
|
||||
|
||||
@ -35,11 +35,13 @@ public:
|
||||
: allNames(allNames), type(type) {
|
||||
}
|
||||
|
||||
T& create(const std::string& id) {
|
||||
T& create(const std::string& id, bool* created = nullptr) {
|
||||
auto found = defs.find(id);
|
||||
if (found != defs.end()) {
|
||||
if (created) *created = false;
|
||||
return *found->second;
|
||||
}
|
||||
if (created) *created = true;
|
||||
checkIdentifier(id);
|
||||
allNames[id] = type;
|
||||
names.push_back(id);
|
||||
|
||||
@ -187,11 +187,49 @@ static void perform_user_block_fields(
|
||||
layout = StructLayout::create(fields);
|
||||
}
|
||||
|
||||
static void process_method(
|
||||
dv::value& properties,
|
||||
const std::string& method,
|
||||
const std::string& name,
|
||||
const dv::value& value
|
||||
) {
|
||||
if (method == "append") {
|
||||
if (!properties.has(name)) {
|
||||
properties[name] = dv::list();
|
||||
}
|
||||
auto& list = properties[name];
|
||||
if (value.isList()) {
|
||||
for (const auto& item : value) {
|
||||
list.add(item);
|
||||
}
|
||||
} else {
|
||||
list.add(value);
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
"unknown method " + method + " for " + name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentLoader::loadBlock(
|
||||
Block& def, const std::string& name, const fs::path& file
|
||||
) {
|
||||
auto root = files::read_json(file);
|
||||
def.properties = root;
|
||||
if (def.properties == nullptr) {
|
||||
def.properties = dv::object();
|
||||
def.properties["name"] = name;
|
||||
}
|
||||
for (auto& [key, value] : root.asObject()) {
|
||||
auto pos = key.rfind('@');
|
||||
if (pos == std::string::npos) {
|
||||
def.properties[key] = value;
|
||||
continue;
|
||||
}
|
||||
auto field = key.substr(0, pos);
|
||||
auto suffix = key.substr(pos + 1);
|
||||
process_method(def.properties, suffix, field, value);
|
||||
}
|
||||
|
||||
if (root.has("parent")) {
|
||||
const auto& parentName = root["parent"].asString();
|
||||
@ -492,7 +530,8 @@ void ContentLoader::loadBlock(
|
||||
if (fs::exists(configFile)) loadBlock(def, full, configFile);
|
||||
|
||||
if (!def.hidden) {
|
||||
auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX);
|
||||
bool created;
|
||||
auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX, &created);
|
||||
item.generated = true;
|
||||
item.caption = def.caption;
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
@ -502,7 +541,7 @@ void ContentLoader::loadBlock(
|
||||
for (uint j = 0; j < 4; j++) {
|
||||
item.emission[j] = def.emission[j];
|
||||
}
|
||||
stats->totalItems++;
|
||||
stats->totalItems += created;
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,9 +603,10 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
if (parent.empty() || builder.blocks.get(parent)) {
|
||||
// No dependency or dependency already loaded/exists in another
|
||||
// content pack
|
||||
auto& def = builder.blocks.create(full);
|
||||
bool created;
|
||||
auto& def = builder.blocks.create(full, &created);
|
||||
loadBlock(def, full, name);
|
||||
stats->totalBlocks++;
|
||||
stats->totalBlocks += created;
|
||||
} else {
|
||||
// Dependency not loaded yet, add to pending items
|
||||
pendingDefs.emplace_back(full, name);
|
||||
@ -583,9 +623,10 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
if (builder.blocks.get(parent)) {
|
||||
// Dependency resolved or parent exists in another pack,
|
||||
// load the item
|
||||
auto& def = builder.blocks.create(it->first);
|
||||
bool created;
|
||||
auto& def = builder.blocks.create(it->first, &created);
|
||||
loadBlock(def, it->first, it->second);
|
||||
stats->totalBlocks++;
|
||||
stats->totalBlocks += created;
|
||||
it = pendingDefs.erase(it); // Remove resolved item
|
||||
progressMade = true;
|
||||
} else {
|
||||
@ -609,9 +650,10 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
if (parent.empty() || builder.items.get(parent)) {
|
||||
// No dependency or dependency already loaded/exists in another
|
||||
// content pack
|
||||
auto& def = builder.items.create(full);
|
||||
bool created;
|
||||
auto& def = builder.items.create(full, &created);
|
||||
loadItem(def, full, name);
|
||||
stats->totalItems++;
|
||||
stats->totalItems += created;
|
||||
} else {
|
||||
// Dependency not loaded yet, add to pending items
|
||||
pendingDefs.emplace_back(full, name);
|
||||
@ -628,9 +670,10 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
if (builder.items.get(parent)) {
|
||||
// Dependency resolved or parent exists in another pack,
|
||||
// load the item
|
||||
auto& def = builder.items.create(it->first);
|
||||
bool created;
|
||||
auto& def = builder.items.create(it->first, &created);
|
||||
loadItem(def, it->first, it->second);
|
||||
stats->totalItems++;
|
||||
stats->totalItems += created;
|
||||
it = pendingDefs.erase(it); // Remove resolved item
|
||||
progressMade = true;
|
||||
} else {
|
||||
@ -654,9 +697,10 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
if (parent.empty() || builder.entities.get(parent)) {
|
||||
// No dependency or dependency already loaded/exists in another
|
||||
// content pack
|
||||
auto& def = builder.entities.create(full);
|
||||
bool created;
|
||||
auto& def = builder.entities.create(full, &created);
|
||||
loadEntity(def, full, name);
|
||||
stats->totalEntities++;
|
||||
stats->totalEntities += created;
|
||||
} else {
|
||||
// Dependency not loaded yet, add to pending items
|
||||
pendingDefs.emplace_back(full, name);
|
||||
@ -673,9 +717,10 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
if (builder.entities.get(parent)) {
|
||||
// Dependency resolved or parent exists in another pack,
|
||||
// load the item
|
||||
auto& def = builder.entities.create(it->first);
|
||||
bool created;
|
||||
auto& def = builder.entities.create(it->first, &created);
|
||||
loadEntity(def, it->first, it->second);
|
||||
stats->totalEntities++;
|
||||
stats->totalEntities += created;
|
||||
it = pendingDefs.erase(it); // Remove resolved item
|
||||
progressMade = true;
|
||||
} else {
|
||||
|
||||
@ -13,9 +13,9 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ContentPack ContentPack::createCore(const EnginePaths* paths) {
|
||||
ContentPack ContentPack::createCore(const EnginePaths& paths) {
|
||||
return ContentPack {
|
||||
"core", "Core", ENGINE_VERSION_STRING, "", "", paths->getResourcesFolder(), {}
|
||||
"core", "Core", ENGINE_VERSION_STRING, "", "", paths.getResourcesFolder(), "res:", {}
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ static void checkContentPackId(const std::string& id, const fs::path& folder) {
|
||||
}
|
||||
}
|
||||
|
||||
ContentPack ContentPack::read(const fs::path& folder) {
|
||||
ContentPack ContentPack::read(const std::string& path, const fs::path& folder) {
|
||||
auto root = files::read_json(folder / fs::path(PACKAGE_FILENAME));
|
||||
ContentPack pack;
|
||||
root.at("id").get(pack.id);
|
||||
@ -90,6 +90,7 @@ ContentPack ContentPack::read(const fs::path& folder) {
|
||||
root.at("description").get(pack.description);
|
||||
root.at("source").get(pack.source);
|
||||
pack.folder = folder;
|
||||
pack.path = path;
|
||||
|
||||
if (auto found = root.at("dependencies")) {
|
||||
const auto& dependencies = *found;
|
||||
@ -123,17 +124,19 @@ ContentPack ContentPack::read(const fs::path& folder) {
|
||||
}
|
||||
|
||||
void ContentPack::scanFolder(
|
||||
const fs::path& folder, std::vector<ContentPack>& packs
|
||||
const std::string& path, const fs::path& folder, std::vector<ContentPack>& packs
|
||||
) {
|
||||
if (!fs::is_directory(folder)) {
|
||||
return;
|
||||
}
|
||||
for (const auto& entry : fs::directory_iterator(folder)) {
|
||||
const fs::path& folder = entry.path();
|
||||
if (!fs::is_directory(folder)) continue;
|
||||
if (!is_pack(folder)) continue;
|
||||
const fs::path& packFolder = entry.path();
|
||||
if (!fs::is_directory(packFolder)) continue;
|
||||
if (!is_pack(packFolder)) continue;
|
||||
try {
|
||||
packs.push_back(read(folder));
|
||||
packs.push_back(
|
||||
read(path + "/" + packFolder.filename().string(), packFolder)
|
||||
);
|
||||
} catch (const contentpack_error& err) {
|
||||
std::cerr << "package.json error at " << err.getFolder().u8string();
|
||||
std::cerr << ": " << err.what() << std::endl;
|
||||
@ -146,9 +149,7 @@ void ContentPack::scanFolder(
|
||||
std::vector<std::string> ContentPack::worldPacksList(const fs::path& folder) {
|
||||
fs::path listfile = folder / fs::path("packs.list");
|
||||
if (!fs::is_regular_file(listfile)) {
|
||||
std::cerr << "warning: packs.list not found (will be created)";
|
||||
std::cerr << std::endl;
|
||||
files::write_string(listfile, "# autogenerated, do not modify\nbase\n");
|
||||
throw std::runtime_error("missing file 'packs.list'");
|
||||
}
|
||||
return files::read_list(listfile);
|
||||
}
|
||||
|
||||
@ -10,18 +10,18 @@
|
||||
|
||||
class EnginePaths;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class contentpack_error : public std::runtime_error {
|
||||
std::string packId;
|
||||
fs::path folder;
|
||||
std::filesystem::path folder;
|
||||
public:
|
||||
contentpack_error(
|
||||
std::string packId, fs::path folder, const std::string& message
|
||||
std::string packId,
|
||||
std::filesystem::path folder,
|
||||
const std::string& message
|
||||
);
|
||||
|
||||
std::string getPackId() const;
|
||||
fs::path getFolder() const;
|
||||
std::filesystem::path getFolder() const;
|
||||
};
|
||||
|
||||
enum class DependencyLevel {
|
||||
@ -42,45 +42,52 @@ struct ContentPack {
|
||||
std::string version = "0.0";
|
||||
std::string creator = "";
|
||||
std::string description = "no description";
|
||||
fs::path folder;
|
||||
std::filesystem::path folder;
|
||||
std::string path;
|
||||
std::vector<DependencyPack> dependencies;
|
||||
std::string source = "";
|
||||
|
||||
fs::path getContentFile() const;
|
||||
std::filesystem::path getContentFile() const;
|
||||
|
||||
static inline const std::string PACKAGE_FILENAME = "package.json";
|
||||
static inline const std::string CONTENT_FILENAME = "content.json";
|
||||
static inline const fs::path BLOCKS_FOLDER = "blocks";
|
||||
static inline const fs::path ITEMS_FOLDER = "items";
|
||||
static inline const fs::path ENTITIES_FOLDER = "entities";
|
||||
static inline const fs::path GENERATORS_FOLDER = "generators";
|
||||
static inline const std::filesystem::path BLOCKS_FOLDER = "blocks";
|
||||
static inline const std::filesystem::path ITEMS_FOLDER = "items";
|
||||
static inline const std::filesystem::path ENTITIES_FOLDER = "entities";
|
||||
static inline const std::filesystem::path GENERATORS_FOLDER = "generators";
|
||||
static const std::vector<std::string> RESERVED_NAMES;
|
||||
|
||||
static bool is_pack(const fs::path& folder);
|
||||
static ContentPack read(const fs::path& folder);
|
||||
|
||||
static void scanFolder(
|
||||
const fs::path& folder, std::vector<ContentPack>& packs
|
||||
static bool is_pack(const std::filesystem::path& folder);
|
||||
static ContentPack read(
|
||||
const std::string& path, const std::filesystem::path& folder
|
||||
);
|
||||
|
||||
static std::vector<std::string> worldPacksList(const fs::path& folder);
|
||||
static void scanFolder(
|
||||
const std::string& path,
|
||||
const std::filesystem::path& folder,
|
||||
std::vector<ContentPack>& packs
|
||||
);
|
||||
|
||||
static fs::path findPack(
|
||||
static std::vector<std::string> worldPacksList(
|
||||
const std::filesystem::path& folder
|
||||
);
|
||||
|
||||
static std::filesystem::path findPack(
|
||||
const EnginePaths* paths,
|
||||
const fs::path& worldDir,
|
||||
const std::filesystem::path& worldDir,
|
||||
const std::string& name
|
||||
);
|
||||
|
||||
static ContentPack createCore(const EnginePaths*);
|
||||
static ContentPack createCore(const EnginePaths&);
|
||||
|
||||
static inline fs::path getFolderFor(ContentType type) {
|
||||
static inline std::filesystem::path getFolderFor(ContentType type) {
|
||||
switch (type) {
|
||||
case ContentType::BLOCK: return ContentPack::BLOCKS_FOLDER;
|
||||
case ContentType::ITEM: return ContentPack::ITEMS_FOLDER;
|
||||
case ContentType::ENTITY: return ContentPack::ENTITIES_FOLDER;
|
||||
case ContentType::GENERATOR: return ContentPack::GENERATORS_FOLDER;
|
||||
case ContentType::NONE: return fs::u8path("");
|
||||
default: return fs::u8path("");
|
||||
case ContentType::NONE: return std::filesystem::u8path("");
|
||||
default: return std::filesystem::u8path("");
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -95,12 +102,13 @@ struct ContentPackStats {
|
||||
}
|
||||
};
|
||||
|
||||
struct world_funcs_set {
|
||||
bool onblockplaced : 1;
|
||||
bool onblockreplaced : 1;
|
||||
bool onblockbroken : 1;
|
||||
bool onblockinteract : 1;
|
||||
bool onplayertick : 1;
|
||||
struct WorldFuncsSet {
|
||||
bool onblockplaced;
|
||||
bool onblockreplaced;
|
||||
bool onblockbreaking;
|
||||
bool onblockbroken;
|
||||
bool onblockinteract;
|
||||
bool onplayertick;
|
||||
};
|
||||
|
||||
class ContentPackRuntime {
|
||||
@ -108,7 +116,7 @@ class ContentPackRuntime {
|
||||
ContentPackStats stats {};
|
||||
scriptenv env;
|
||||
public:
|
||||
world_funcs_set worldfuncsset {};
|
||||
WorldFuncsSet worldfuncsset {};
|
||||
|
||||
ContentPackRuntime(ContentPack info, scriptenv env);
|
||||
~ContentPackRuntime();
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
PacksManager::PacksManager() = default;
|
||||
|
||||
void PacksManager::setSources(std::vector<fs::path> sources) {
|
||||
void PacksManager::setSources(std::vector<std::pair<std::string, fs::path>> sources) {
|
||||
this->sources = std::move(sources);
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ void PacksManager::scan() {
|
||||
packs.clear();
|
||||
|
||||
std::vector<ContentPack> packsList;
|
||||
for (auto& folder : sources) {
|
||||
ContentPack::scanFolder(folder, packsList);
|
||||
for (auto& [path, folder] : sources) {
|
||||
ContentPack::scanFolder(path, folder, packsList);
|
||||
for (auto& pack : packsList) {
|
||||
packs.try_emplace(pack.id, pack);
|
||||
}
|
||||
@ -116,7 +116,7 @@ static bool resolve_dependencies(
|
||||
return satisfied;
|
||||
}
|
||||
|
||||
std::vector<std::string> PacksManager::assembly(
|
||||
std::vector<std::string> PacksManager::assemble(
|
||||
const std::vector<std::string>& names
|
||||
) const {
|
||||
std::vector<std::string> allNames = names;
|
||||
|
||||
@ -10,12 +10,12 @@ namespace fs = std::filesystem;
|
||||
|
||||
class PacksManager {
|
||||
std::unordered_map<std::string, ContentPack> packs;
|
||||
std::vector<fs::path> sources;
|
||||
std::vector<std::pair<std::string, fs::path>> sources;
|
||||
public:
|
||||
PacksManager();
|
||||
|
||||
/// @brief Set content packs sources (search folders)
|
||||
void setSources(std::vector<fs::path> sources);
|
||||
void setSources(std::vector<std::pair<std::string, fs::path>> sources);
|
||||
|
||||
/// @brief Scan sources and collect all found packs excluding duplication.
|
||||
/// Scanning order depends on sources order
|
||||
@ -38,7 +38,7 @@ public:
|
||||
/// @return resulting ordered vector of pack names
|
||||
/// @throws contentpack_error if required dependency not found or
|
||||
/// circular dependency detected
|
||||
std::vector<std::string> assembly(const std::vector<std::string>& names
|
||||
std::vector<std::string> assemble(const std::vector<std::string>& names
|
||||
) const;
|
||||
|
||||
/// @brief Collect all pack names (identifiers) into a new vector
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
#include "voxels/Block.hpp"
|
||||
|
||||
// All in-game definitions (blocks, items, etc..)
|
||||
void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
void corecontent::setup(const EnginePaths& paths, ContentBuilder& builder) {
|
||||
{
|
||||
Block& block = builder->blocks.create(CORE_AIR);
|
||||
Block& block = builder.blocks.create(CORE_AIR);
|
||||
block.replaceable = true;
|
||||
block.drawGroup = 1;
|
||||
block.lightPassing = true;
|
||||
@ -24,11 +24,11 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
block.pickingItem = CORE_EMPTY;
|
||||
}
|
||||
{
|
||||
ItemDef& item = builder->items.create(CORE_EMPTY);
|
||||
ItemDef& item = builder.items.create(CORE_EMPTY);
|
||||
item.iconType = ItemIconType::NONE;
|
||||
}
|
||||
|
||||
auto bindsFile = paths->getResourcesFolder()/fs::path("bindings.toml");
|
||||
auto bindsFile = paths.getResourcesFolder()/fs::path("bindings.toml");
|
||||
if (fs::is_regular_file(bindsFile)) {
|
||||
Events::loadBindings(
|
||||
bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND
|
||||
@ -36,20 +36,20 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
}
|
||||
|
||||
{
|
||||
Block& block = builder->blocks.create(CORE_OBSTACLE);
|
||||
Block& block = builder.blocks.create(CORE_OBSTACLE);
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
block.textureFaces[i] = "obstacle";
|
||||
}
|
||||
block.hitboxes = {AABB()};
|
||||
block.breakable = false;
|
||||
ItemDef& item = builder->items.create(CORE_OBSTACLE+".item");
|
||||
ItemDef& item = builder.items.create(CORE_OBSTACLE+".item");
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = CORE_OBSTACLE;
|
||||
item.placingBlock = CORE_OBSTACLE;
|
||||
item.caption = block.caption;
|
||||
}
|
||||
{
|
||||
Block& block = builder->blocks.create(CORE_STRUCT_AIR);
|
||||
Block& block = builder.blocks.create(CORE_STRUCT_AIR);
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
block.textureFaces[i] = "struct_air";
|
||||
}
|
||||
@ -58,7 +58,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
block.lightPassing = true;
|
||||
block.hitboxes = {AABB()};
|
||||
block.obstacle = false;
|
||||
ItemDef& item = builder->items.create(CORE_STRUCT_AIR+".item");
|
||||
ItemDef& item = builder.items.create(CORE_STRUCT_AIR+".item");
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = CORE_STRUCT_AIR;
|
||||
item.placingBlock = CORE_STRUCT_AIR;
|
||||
|
||||
@ -21,12 +21,9 @@ inline const std::string BIND_MOVE_CROUCH = "movement.crouch";
|
||||
inline const std::string BIND_MOVE_CHEAT = "movement.cheat";
|
||||
inline const std::string BIND_CAM_ZOOM = "camera.zoom";
|
||||
inline const std::string BIND_CAM_MODE = "camera.mode";
|
||||
inline const std::string BIND_PLAYER_NOCLIP = "player.noclip";
|
||||
inline const std::string BIND_PLAYER_FLIGHT = "player.flight";
|
||||
inline const std::string BIND_PLAYER_ATTACK = "player.attack";
|
||||
inline const std::string BIND_PLAYER_DESTROY = "player.destroy";
|
||||
inline const std::string BIND_PLAYER_BUILD = "player.build";
|
||||
inline const std::string BIND_PLAYER_PICK = "player.pick";
|
||||
inline const std::string BIND_PLAYER_FAST_INTERACTOIN =
|
||||
"player.fast_interaction";
|
||||
inline const std::string BIND_HUD_INVENTORY = "hud.inventory";
|
||||
@ -35,5 +32,5 @@ class EnginePaths;
|
||||
class ContentBuilder;
|
||||
|
||||
namespace corecontent {
|
||||
void setup(EnginePaths* paths, ContentBuilder* builder);
|
||||
void setup(const EnginePaths& paths, ContentBuilder& builder);
|
||||
}
|
||||
|
||||
@ -95,8 +95,8 @@ static inline FieldIncapatibilityType checkIncapatibility(
|
||||
|
||||
static inline integer_t clamp_value(integer_t value, FieldType type) {
|
||||
auto typesize = sizeof_type(type) * CHAR_BIT;
|
||||
integer_t minval = -(1 << (typesize-1));
|
||||
integer_t maxval = (1 << (typesize-1))-1;
|
||||
integer_t minval = -(1LL << (typesize-1));
|
||||
integer_t maxval = (1LL << (typesize-1))-1;
|
||||
return std::min(maxval, std::max(minval, value));
|
||||
}
|
||||
|
||||
|
||||
@ -23,10 +23,16 @@ Logger::Logger(std::string name) : name(std::move(name)) {
|
||||
void Logger::log(
|
||||
LogLevel level, const std::string& name, const std::string& message
|
||||
) {
|
||||
if (level == LogLevel::print) {
|
||||
std::cout << "[" << name << "] " << message << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
std::stringstream ss;
|
||||
switch (level) {
|
||||
case LogLevel::print:
|
||||
case LogLevel::debug:
|
||||
#ifdef NDEBUG
|
||||
return;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include <sstream>
|
||||
|
||||
namespace debug {
|
||||
enum class LogLevel { debug, info, warning, error };
|
||||
enum class LogLevel { print, debug, info, warning, error };
|
||||
|
||||
class Logger;
|
||||
|
||||
@ -60,5 +60,10 @@ namespace debug {
|
||||
LogMessage warning() {
|
||||
return LogMessage(this, LogLevel::warning);
|
||||
}
|
||||
|
||||
/// @brief Print-debugging tool (printed without header)
|
||||
LogMessage print() {
|
||||
return LogMessage(this, LogLevel::print);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ using runnable = std::function<void()>;
|
||||
template<class T> using supplier = std::function<T()>;
|
||||
template<class T> using consumer = std::function<void(T)>;
|
||||
|
||||
using KeyCallback = std::function<bool()>;
|
||||
|
||||
// data sources
|
||||
using wstringsupplier = std::function<std::wstring()>;
|
||||
using doublesupplier = std::function<double()>;
|
||||
|
||||
@ -32,7 +32,6 @@ static std::unique_ptr<FontStylesScheme> build_styles(
|
||||
continue;
|
||||
}
|
||||
if (token.start.pos > offset) {
|
||||
int n = token.start.pos - offset;
|
||||
styles.map.insert(styles.map.end(), token.start.pos - offset, 0);
|
||||
}
|
||||
offset = token.end.pos;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "engine.hpp"
|
||||
#include "Engine.hpp"
|
||||
|
||||
#ifndef GLEW_STATIC
|
||||
#define GLEW_STATIC
|
||||
#endif
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "assets/AssetsLoader.hpp"
|
||||
@ -15,13 +17,10 @@
|
||||
#include "content/ContentLoader.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "files/files.hpp"
|
||||
#include "files/settings_io.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
#include "frontend/screens/Screen.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "graphics/render/ModelsGenerator.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
@ -30,6 +29,7 @@
|
||||
#include "logic/EngineController.hpp"
|
||||
#include "logic/CommandsInterpreter.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/scripting/scripting_hud.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
#include "util/platform.hpp"
|
||||
@ -37,7 +37,9 @@
|
||||
#include "window/Events.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "Mainloop.hpp"
|
||||
#include "ServerMainloop.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
@ -71,52 +73,69 @@ static std::unique_ptr<ImageData> load_icon(const fs::path& resdir) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths)
|
||||
: settings(settings), settingsHandler(settingsHandler), paths(paths),
|
||||
Engine::Engine(CoreParameters coreParameters)
|
||||
: params(std::move(coreParameters)),
|
||||
settings(),
|
||||
settingsHandler({settings}),
|
||||
interpreter(std::make_unique<cmd::CommandsInterpreter>()),
|
||||
network(network::Network::create(settings.network))
|
||||
{
|
||||
paths->prepare();
|
||||
network(network::Network::create(settings.network)) {
|
||||
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
|
||||
if (params.headless) {
|
||||
logger.info() << "headless mode is enabled";
|
||||
}
|
||||
paths.setResourcesFolder(params.resFolder);
|
||||
paths.setUserFilesFolder(params.userFolder);
|
||||
paths.prepare();
|
||||
if (!params.scriptFile.empty()) {
|
||||
paths.setScriptFolder(params.scriptFile.parent_path());
|
||||
}
|
||||
loadSettings();
|
||||
|
||||
auto resdir = paths->getResourcesFolder();
|
||||
auto resdir = paths.getResourcesFolder();
|
||||
|
||||
controller = std::make_unique<EngineController>(this);
|
||||
if (Window::initialize(&this->settings.display)){
|
||||
throw initialize_error("could not initialize window");
|
||||
controller = std::make_unique<EngineController>(*this);
|
||||
if (!params.headless) {
|
||||
if (Window::initialize(&settings.display)){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
time.set(Window::time());
|
||||
if (auto icon = load_icon(resdir)) {
|
||||
icon->flipY();
|
||||
Window::setIcon(icon.get());
|
||||
}
|
||||
loadControls();
|
||||
|
||||
gui = std::make_unique<gui::GUI>();
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
menus::create_version_label(*this);
|
||||
}
|
||||
}
|
||||
if (auto icon = load_icon(resdir)) {
|
||||
icon->flipY();
|
||||
Window::setIcon(icon.get());
|
||||
}
|
||||
loadControls();
|
||||
audio::initialize(settings.audio.enabled.get());
|
||||
audio::initialize(settings.audio.enabled.get() && !params.headless);
|
||||
create_channel(this, "master", settings.audio.volumeMaster);
|
||||
create_channel(this, "regular", settings.audio.volumeRegular);
|
||||
create_channel(this, "music", settings.audio.volumeMusic);
|
||||
create_channel(this, "ambient", settings.audio.volumeAmbient);
|
||||
create_channel(this, "ui", settings.audio.volumeUI);
|
||||
|
||||
gui = std::make_unique<gui::GUI>();
|
||||
if (settings.ui.language.get() == "auto") {
|
||||
bool langNotSet = settings.ui.language.get() == "auto";
|
||||
if (langNotSet) {
|
||||
settings.ui.language.set(langs::locale_by_envlocale(
|
||||
platform::detect_locale(),
|
||||
paths->getResourcesFolder()
|
||||
paths.getResourcesFolder()
|
||||
));
|
||||
}
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
menus::create_version_label(this);
|
||||
scripting::initialize(this);
|
||||
if (!isHeadless()) {
|
||||
gui->setPageLoader(scripting::create_page_loader());
|
||||
}
|
||||
keepAlive(settings.ui.language.observe([=](auto lang) {
|
||||
keepAlive(settings.ui.language.observe([this](auto lang) {
|
||||
setLanguage(lang);
|
||||
}, true));
|
||||
|
||||
scripting::initialize(this);
|
||||
basePacks = files::read_list(resdir/fs::path("config/builtins.list"));
|
||||
}
|
||||
|
||||
void Engine::loadSettings() {
|
||||
fs::path settings_file = paths->getSettingsFile();
|
||||
fs::path settings_file = paths.getSettingsFile();
|
||||
if (fs::is_regular_file(settings_file)) {
|
||||
logger.info() << "loading settings";
|
||||
std::string text = files::read_string(settings_file);
|
||||
@ -130,7 +149,7 @@ void Engine::loadSettings() {
|
||||
}
|
||||
|
||||
void Engine::loadControls() {
|
||||
fs::path controls_file = paths->getControlsFile();
|
||||
fs::path controls_file = paths.getControlsFile();
|
||||
if (fs::is_regular_file(controls_file)) {
|
||||
logger.info() << "loading controls";
|
||||
std::string text = files::read_string(controls_file);
|
||||
@ -143,13 +162,6 @@ void Engine::onAssetsLoaded() {
|
||||
gui->onAssetsLoad(assets.get());
|
||||
}
|
||||
|
||||
void Engine::updateTimers() {
|
||||
frame++;
|
||||
double currentTime = Window::time();
|
||||
delta = currentTime - lastTime;
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
void Engine::updateHotkeys() {
|
||||
if (Events::jpressed(keycode::F2)) {
|
||||
saveScreenshot();
|
||||
@ -162,67 +174,58 @@ void Engine::updateHotkeys() {
|
||||
void Engine::saveScreenshot() {
|
||||
auto image = Window::takeScreenshot();
|
||||
image->flipY();
|
||||
fs::path filename = paths->getNewScreenshotFile("png");
|
||||
fs::path filename = paths.getNewScreenshotFile("png");
|
||||
imageio::write(filename.string(), image.get());
|
||||
logger.info() << "saved screenshot as " << filename.u8string();
|
||||
}
|
||||
|
||||
void Engine::mainloop() {
|
||||
logger.info() << "starting menu screen";
|
||||
setScreen(std::make_shared<MenuScreen>(this));
|
||||
|
||||
Batch2D batch(1024);
|
||||
lastTime = Window::time();
|
||||
|
||||
logger.info() << "engine started";
|
||||
while (!Window::isShouldClose()){
|
||||
assert(screen != nullptr);
|
||||
updateTimers();
|
||||
updateHotkeys();
|
||||
audio::update(delta);
|
||||
|
||||
gui->act(delta, Viewport(Window::width, Window::height));
|
||||
screen->update(delta);
|
||||
|
||||
if (!Window::isIconified()) {
|
||||
renderFrame(batch);
|
||||
}
|
||||
Window::setFramerate(
|
||||
Window::isIconified() && settings.display.limitFpsIconified.get()
|
||||
? 20
|
||||
: settings.display.framerate.get()
|
||||
);
|
||||
|
||||
network->update();
|
||||
processPostRunnables();
|
||||
|
||||
Window::swapBuffers();
|
||||
Events::pollEvents();
|
||||
void Engine::run() {
|
||||
if (params.headless) {
|
||||
ServerMainloop(*this).run();
|
||||
} else {
|
||||
Mainloop(*this).run();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::renderFrame(Batch2D& batch) {
|
||||
screen->draw(delta);
|
||||
void Engine::postUpdate() {
|
||||
network->update();
|
||||
postRunnables.run();
|
||||
scripting::process_post_runnables();
|
||||
}
|
||||
|
||||
void Engine::updateFrontend() {
|
||||
double delta = time.getDelta();
|
||||
updateHotkeys();
|
||||
audio::update(delta);
|
||||
gui->act(delta, Viewport(Window::width, Window::height));
|
||||
screen->update(delta);
|
||||
}
|
||||
|
||||
void Engine::nextFrame() {
|
||||
Window::setFramerate(
|
||||
Window::isIconified() && settings.display.limitFpsIconified.get()
|
||||
? 20
|
||||
: settings.display.framerate.get()
|
||||
);
|
||||
Window::swapBuffers();
|
||||
Events::pollEvents();
|
||||
}
|
||||
|
||||
void Engine::renderFrame() {
|
||||
screen->draw(time.getDelta());
|
||||
|
||||
Viewport viewport(Window::width, Window::height);
|
||||
DrawContext ctx(nullptr, viewport, &batch);
|
||||
DrawContext ctx(nullptr, viewport, nullptr);
|
||||
gui->draw(ctx, *assets);
|
||||
}
|
||||
|
||||
void Engine::processPostRunnables() {
|
||||
std::lock_guard<std::recursive_mutex> lock(postRunnablesMutex);
|
||||
while (!postRunnables.empty()) {
|
||||
postRunnables.front()();
|
||||
postRunnables.pop();
|
||||
}
|
||||
scripting::process_post_runnables();
|
||||
}
|
||||
|
||||
void Engine::saveSettings() {
|
||||
logger.info() << "saving settings";
|
||||
files::write_string(paths->getSettingsFile(), toml::stringify(settingsHandler));
|
||||
logger.info() << "saving bindings";
|
||||
files::write_string(paths->getControlsFile(), Events::writeBindings());
|
||||
files::write_string(paths.getSettingsFile(), toml::stringify(settingsHandler));
|
||||
if (!params.headless) {
|
||||
logger.info() << "saving bindings";
|
||||
files::write_string(paths.getControlsFile(), Events::writeBindings());
|
||||
}
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
@ -235,13 +238,19 @@ Engine::~Engine() {
|
||||
content.reset();
|
||||
assets.reset();
|
||||
interpreter.reset();
|
||||
gui.reset();
|
||||
logger.info() << "gui finished";
|
||||
if (gui) {
|
||||
gui.reset();
|
||||
logger.info() << "gui finished";
|
||||
}
|
||||
audio::close();
|
||||
network.reset();
|
||||
clearKeepedObjects();
|
||||
scripting::close();
|
||||
logger.info() << "scripting finished";
|
||||
Window::terminate();
|
||||
if (!params.headless) {
|
||||
Window::terminate();
|
||||
logger.info() << "window closed";
|
||||
}
|
||||
logger.info() << "engine finished";
|
||||
}
|
||||
|
||||
@ -256,13 +265,17 @@ cmd::CommandsInterpreter* Engine::getCommandsInterpreter() {
|
||||
PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
|
||||
PacksManager manager;
|
||||
manager.setSources({
|
||||
worldFolder/fs::path("content"),
|
||||
paths->getUserFilesFolder()/fs::path("content"),
|
||||
paths->getResourcesFolder()/fs::path("content")
|
||||
{"world:content", worldFolder.empty() ? worldFolder : worldFolder/fs::path("content")},
|
||||
{"user:content", paths.getUserFilesFolder()/fs::path("content")},
|
||||
{"res:content", paths.getResourcesFolder()/fs::path("content")}
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
void Engine::setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer) {
|
||||
this->levelConsumer = std::move(levelConsumer);
|
||||
}
|
||||
|
||||
void Engine::loadAssets() {
|
||||
logger.info() << "loading assets";
|
||||
Shader::preprocessor->setPaths(resPaths.get());
|
||||
@ -278,42 +291,36 @@ void Engine::loadAssets() {
|
||||
auto task = loader.startTask([=](){});
|
||||
task->waitForEnd();
|
||||
} else {
|
||||
try {
|
||||
while (loader.hasNext()) {
|
||||
loader.loadNext();
|
||||
}
|
||||
} catch (const assetload::error& err) {
|
||||
new_assets.reset();
|
||||
throw;
|
||||
while (loader.hasNext()) {
|
||||
loader.loadNext();
|
||||
}
|
||||
}
|
||||
assets = std::move(new_assets);
|
||||
|
||||
if (content) {
|
||||
for (auto& [name, def] : content->blocks.getDefs()) {
|
||||
if (def->model == BlockModel::custom) {
|
||||
if (def->modelName.empty()) {
|
||||
assets->store(
|
||||
std::make_unique<model::Model>(
|
||||
ModelsGenerator::loadCustomBlockModel(
|
||||
def->customModelRaw, *assets, !def->shadeless
|
||||
)
|
||||
),
|
||||
name + ".model"
|
||||
);
|
||||
def->modelName = def->name + ".model";
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& [name, def] : content->items.getDefs()) {
|
||||
|
||||
if (content == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (auto& [name, def] : content->blocks.getDefs()) {
|
||||
if (def->model == BlockModel::custom && def->modelName.empty()) {
|
||||
assets->store(
|
||||
std::make_unique<model::Model>(
|
||||
ModelsGenerator::generate(*def, *content, *assets)
|
||||
ModelsGenerator::loadCustomBlockModel(
|
||||
def->customModelRaw, *assets, !def->shadeless
|
||||
)
|
||||
),
|
||||
name + ".model"
|
||||
);
|
||||
def->modelName = def->name + ".model";
|
||||
}
|
||||
}
|
||||
for (auto& [name, def] : content->items.getDefs()) {
|
||||
assets->store(
|
||||
std::make_unique<model::Model>(
|
||||
ModelsGenerator::generate(*def, *content, *assets)
|
||||
),
|
||||
name + ".model"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void load_configs(const fs::path& root) {
|
||||
@ -329,7 +336,7 @@ static void load_configs(const fs::path& root) {
|
||||
void Engine::loadContent() {
|
||||
scripting::cleanup();
|
||||
|
||||
auto resdir = paths->getResourcesFolder();
|
||||
auto resdir = paths.getResourcesFolder();
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto& pack : contentPacks) {
|
||||
@ -337,12 +344,12 @@ void Engine::loadContent() {
|
||||
}
|
||||
|
||||
ContentBuilder contentBuilder;
|
||||
corecontent::setup(paths, &contentBuilder);
|
||||
corecontent::setup(paths, contentBuilder);
|
||||
|
||||
paths->setContentPacks(&contentPacks);
|
||||
PacksManager manager = createPacksManager(paths->getCurrentWorldFolder());
|
||||
paths.setContentPacks(&contentPacks);
|
||||
PacksManager manager = createPacksManager(paths.getCurrentWorldFolder());
|
||||
manager.scan();
|
||||
names = manager.assembly(names);
|
||||
names = manager.assemble(names);
|
||||
contentPacks = manager.getAll(names);
|
||||
|
||||
auto corePack = ContentPack::createCore(paths);
|
||||
@ -372,13 +379,15 @@ void Engine::loadContent() {
|
||||
ContentLoader::loadScripts(*content);
|
||||
|
||||
langs::setup(resdir, langs::current->getId(), contentPacks);
|
||||
loadAssets();
|
||||
onAssetsLoaded();
|
||||
if (!isHeadless()) {
|
||||
loadAssets();
|
||||
onAssetsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::resetContent() {
|
||||
scripting::cleanup();
|
||||
auto resdir = paths->getResourcesFolder();
|
||||
auto resdir = paths.getResourcesFolder();
|
||||
std::vector<PathsRoot> resRoots;
|
||||
{
|
||||
auto pack = ContentPack::createCore(paths);
|
||||
@ -395,8 +404,10 @@ void Engine::resetContent() {
|
||||
content.reset();
|
||||
|
||||
langs::setup(resdir, langs::current->getId(), contentPacks);
|
||||
loadAssets();
|
||||
onAssetsLoaded();
|
||||
if (!isHeadless()) {
|
||||
loadAssets();
|
||||
onAssetsLoaded();
|
||||
}
|
||||
|
||||
contentPacks = manager.getAll(basePacks);
|
||||
}
|
||||
@ -405,26 +416,23 @@ void Engine::loadWorldContent(const fs::path& folder) {
|
||||
contentPacks.clear();
|
||||
auto packNames = ContentPack::worldPacksList(folder);
|
||||
PacksManager manager;
|
||||
manager.setSources({
|
||||
folder/fs::path("content"),
|
||||
paths->getUserFilesFolder()/fs::path("content"),
|
||||
paths->getResourcesFolder()/fs::path("content")
|
||||
});
|
||||
manager.setSources(
|
||||
{{"world:content",
|
||||
folder.empty() ? folder : folder / fs::path("content")},
|
||||
{"user:content", paths.getUserFilesFolder() / fs::path("content")},
|
||||
{"res:content", paths.getResourcesFolder() / fs::path("content")}}
|
||||
);
|
||||
manager.scan();
|
||||
contentPacks = manager.getAll(manager.assembly(packNames));
|
||||
paths->setCurrentWorldFolder(folder);
|
||||
contentPacks = manager.getAll(manager.assemble(packNames));
|
||||
paths.setCurrentWorldFolder(folder);
|
||||
loadContent();
|
||||
}
|
||||
|
||||
void Engine::loadAllPacks() {
|
||||
PacksManager manager = createPacksManager(paths->getCurrentWorldFolder());
|
||||
PacksManager manager = createPacksManager(paths.getCurrentWorldFolder());
|
||||
manager.scan();
|
||||
auto allnames = manager.getAllNames();
|
||||
contentPacks = manager.getAll(manager.assembly(allnames));
|
||||
}
|
||||
|
||||
double Engine::getDelta() const {
|
||||
return delta;
|
||||
contentPacks = manager.getAll(manager.assemble(allnames));
|
||||
}
|
||||
|
||||
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
@ -435,8 +443,28 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
}
|
||||
|
||||
void Engine::setLanguage(std::string locale) {
|
||||
langs::setup(paths->getResourcesFolder(), std::move(locale), contentPacks);
|
||||
gui->getMenu()->setPageLoader(menus::create_page_loader(this));
|
||||
langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks);
|
||||
}
|
||||
|
||||
void Engine::onWorldOpen(std::unique_ptr<Level> level) {
|
||||
logger.info() << "world open";
|
||||
levelConsumer(std::move(level));
|
||||
}
|
||||
|
||||
void Engine::onWorldClosed() {
|
||||
logger.info() << "world closed";
|
||||
levelConsumer(nullptr);
|
||||
}
|
||||
|
||||
void Engine::quit() {
|
||||
quitSignal = true;
|
||||
if (!isHeadless()) {
|
||||
Window::setShouldClose(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::isQuitSignal() const {
|
||||
return quitSignal;
|
||||
}
|
||||
|
||||
gui::GUI* Engine::getGUI() {
|
||||
@ -469,7 +497,7 @@ std::vector<std::string>& Engine::getBasePacks() {
|
||||
return basePacks;
|
||||
}
|
||||
|
||||
EnginePaths* Engine::getPaths() {
|
||||
EnginePaths& Engine::getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@ -481,11 +509,6 @@ std::shared_ptr<Screen> Engine::getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
void Engine::postRunnable(const runnable& callback) {
|
||||
std::lock_guard<std::recursive_mutex> lock(postRunnablesMutex);
|
||||
postRunnables.push(callback);
|
||||
}
|
||||
|
||||
SettingsHandler& Engine::getSettingsHandler() {
|
||||
return settingsHandler;
|
||||
}
|
||||
@ -493,3 +516,15 @@ SettingsHandler& Engine::getSettingsHandler() {
|
||||
network::Network& Engine::getNetwork() {
|
||||
return *network;
|
||||
}
|
||||
|
||||
Time& Engine::getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
const CoreParameters& Engine::getCoreParameters() const {
|
||||
return params;
|
||||
}
|
||||
|
||||
bool Engine::isHeadless() const {
|
||||
return params.headless;
|
||||
}
|
||||
@ -2,32 +2,32 @@
|
||||
|
||||
#include "delegates.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "content/content_fwd.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
#include "content/PacksManager.hpp"
|
||||
#include "files/engine_paths.hpp"
|
||||
#include "files/settings_io.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
#include "PostRunnables.hpp"
|
||||
#include "Time.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
class Level;
|
||||
class Screen;
|
||||
class EnginePaths;
|
||||
class ResPaths;
|
||||
class Batch2D;
|
||||
class EngineController;
|
||||
class SettingsHandler;
|
||||
struct EngineSettings;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace gui {
|
||||
class GUI;
|
||||
}
|
||||
@ -45,44 +45,52 @@ public:
|
||||
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
struct CoreParameters {
|
||||
bool headless = false;
|
||||
bool testMode = false;
|
||||
std::filesystem::path resFolder {"res"};
|
||||
std::filesystem::path userFolder {"."};
|
||||
std::filesystem::path scriptFile;
|
||||
};
|
||||
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
EngineSettings& settings;
|
||||
SettingsHandler& settingsHandler;
|
||||
EnginePaths* paths;
|
||||
CoreParameters params;
|
||||
EngineSettings settings;
|
||||
SettingsHandler settingsHandler;
|
||||
EnginePaths paths;
|
||||
|
||||
std::unique_ptr<Assets> assets;
|
||||
std::shared_ptr<Screen> screen;
|
||||
std::vector<ContentPack> contentPacks;
|
||||
std::unique_ptr<Content> content;
|
||||
std::unique_ptr<ResPaths> resPaths;
|
||||
std::queue<runnable> postRunnables;
|
||||
std::recursive_mutex postRunnablesMutex;
|
||||
std::unique_ptr<EngineController> controller;
|
||||
std::unique_ptr<cmd::CommandsInterpreter> interpreter;
|
||||
std::unique_ptr<network::Network> network;
|
||||
std::vector<std::string> basePacks;
|
||||
|
||||
uint64_t frame = 0;
|
||||
double lastTime = 0.0;
|
||||
double delta = 0.0;
|
||||
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
PostRunnables postRunnables;
|
||||
Time time;
|
||||
consumer<std::unique_ptr<Level>> levelConsumer;
|
||||
bool quitSignal = false;
|
||||
|
||||
void loadControls();
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
void updateTimers();
|
||||
void updateHotkeys();
|
||||
void renderFrame(Batch2D& batch);
|
||||
void processPostRunnables();
|
||||
void loadAssets();
|
||||
public:
|
||||
Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths);
|
||||
Engine(CoreParameters coreParameters);
|
||||
~Engine();
|
||||
|
||||
/// @brief Start main engine input/update/render loop.
|
||||
/// Automatically sets MenuScreen
|
||||
void mainloop();
|
||||
|
||||
/// @brief Start the engine
|
||||
void run();
|
||||
|
||||
void postUpdate();
|
||||
|
||||
void updateFrontend();
|
||||
void renderFrame();
|
||||
void nextFrame();
|
||||
|
||||
/// @brief Called after assets loading when all engine systems are initialized
|
||||
void onAssetsLoaded();
|
||||
@ -100,6 +108,7 @@ public:
|
||||
/// @brief Load all selected content-packs and reload assets
|
||||
void loadContent();
|
||||
|
||||
/// @brief Reset content to base packs list
|
||||
void resetContent();
|
||||
|
||||
/// @brief Collect world content-packs and load content
|
||||
@ -110,9 +119,6 @@ public:
|
||||
/// @brief Collect all available content-packs from res/content
|
||||
void loadAllPacks();
|
||||
|
||||
/// @brief Get current frame delta-time
|
||||
double getDelta() const;
|
||||
|
||||
/// @brief Get active assets storage instance
|
||||
Assets* getAssets();
|
||||
|
||||
@ -123,11 +129,18 @@ public:
|
||||
EngineSettings& getSettings();
|
||||
|
||||
/// @brief Get engine filesystem paths source
|
||||
EnginePaths* getPaths();
|
||||
EnginePaths& getPaths();
|
||||
|
||||
/// @brief Get engine resource paths controller
|
||||
ResPaths* getResPaths();
|
||||
|
||||
void onWorldOpen(std::unique_ptr<Level> level);
|
||||
void onWorldClosed();
|
||||
|
||||
void quit();
|
||||
|
||||
bool isQuitSignal() const;
|
||||
|
||||
/// @brief Get current Content instance
|
||||
const Content* getContent() const;
|
||||
|
||||
@ -142,7 +155,9 @@ public:
|
||||
std::shared_ptr<Screen> getScreen();
|
||||
|
||||
/// @brief Enqueue function call to the end of current frame in draw thread
|
||||
void postRunnable(const runnable& callback);
|
||||
void postRunnable(const runnable& callback) {
|
||||
postRunnables.postRunnable(callback);
|
||||
}
|
||||
|
||||
void saveScreenshot();
|
||||
|
||||
@ -151,7 +166,15 @@ public:
|
||||
|
||||
PacksManager createPacksManager(const fs::path& worldFolder);
|
||||
|
||||
void setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer);
|
||||
|
||||
SettingsHandler& getSettingsHandler();
|
||||
|
||||
network::Network& getNetwork();
|
||||
|
||||
Time& getTime();
|
||||
|
||||
const CoreParameters& getCoreParameters() const;
|
||||
|
||||
bool isHeadless() const;
|
||||
};
|
||||
43
src/engine/Mainloop.cpp
Normal file
43
src/engine/Mainloop.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "Mainloop.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "frontend/screens/LevelScreen.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
static debug::Logger logger("mainloop");
|
||||
|
||||
Mainloop::Mainloop(Engine& engine) : engine(engine) {
|
||||
}
|
||||
|
||||
void Mainloop::run() {
|
||||
auto& time = engine.getTime();
|
||||
|
||||
engine.setLevelConsumer([this](auto level) {
|
||||
if (level == nullptr) {
|
||||
// destroy LevelScreen and run quit callbacks
|
||||
engine.setScreen(nullptr);
|
||||
// create and go to menu screen
|
||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||
} else {
|
||||
engine.setScreen(std::make_shared<LevelScreen>(engine, std::move(level)));
|
||||
}
|
||||
});
|
||||
|
||||
logger.info() << "starting menu screen";
|
||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||
|
||||
logger.info() << "main loop started";
|
||||
while (!Window::isShouldClose()){
|
||||
time.update(Window::time());
|
||||
engine.updateFrontend();
|
||||
if (!Window::isIconified()) {
|
||||
engine.renderFrame();
|
||||
}
|
||||
engine.postUpdate();
|
||||
engine.nextFrame();
|
||||
}
|
||||
logger.info() << "main loop stopped";
|
||||
}
|
||||
11
src/engine/Mainloop.hpp
Normal file
11
src/engine/Mainloop.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class Engine;
|
||||
|
||||
class Mainloop {
|
||||
Engine& engine;
|
||||
public:
|
||||
Mainloop(Engine& engine);
|
||||
|
||||
void run();
|
||||
};
|
||||
29
src/engine/PostRunnables.hpp
Normal file
29
src/engine/PostRunnables.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include "delegates.hpp"
|
||||
|
||||
class PostRunnables {
|
||||
std::queue<runnable> runnables;
|
||||
std::recursive_mutex mutex;
|
||||
public:
|
||||
void postRunnable(runnable task) {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
runnables.push(std::move(task));
|
||||
}
|
||||
|
||||
void run() {
|
||||
std::queue<runnable> tasksToRun;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
std::swap(tasksToRun, runnables);
|
||||
}
|
||||
|
||||
while (!tasksToRun.empty()) {
|
||||
auto& task = tasksToRun.front();
|
||||
task();
|
||||
tasksToRun.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
86
src/engine/ServerMainloop.cpp
Normal file
86
src/engine/ServerMainloop.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include "ServerMainloop.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "interfaces/Process.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "util/platform.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
static debug::Logger logger("mainloop");
|
||||
|
||||
inline constexpr int TPS = 20;
|
||||
|
||||
ServerMainloop::ServerMainloop(Engine& engine) : engine(engine) {
|
||||
}
|
||||
|
||||
ServerMainloop::~ServerMainloop() = default;
|
||||
|
||||
void ServerMainloop::run() {
|
||||
const auto& coreParams = engine.getCoreParameters();
|
||||
auto& time = engine.getTime();
|
||||
|
||||
if (coreParams.scriptFile.empty()) {
|
||||
logger.info() << "nothing to do";
|
||||
return;
|
||||
}
|
||||
engine.setLevelConsumer([this](auto level) {
|
||||
setLevel(std::move(level));
|
||||
});
|
||||
|
||||
logger.info() << "starting test " << coreParams.scriptFile;
|
||||
auto process = scripting::start_coroutine(coreParams.scriptFile);
|
||||
|
||||
double targetDelta = 1.0 / static_cast<double>(TPS);
|
||||
double delta = targetDelta;
|
||||
auto begin = system_clock::now();
|
||||
auto startupTime = begin;
|
||||
|
||||
while (process->isActive()) {
|
||||
if (engine.isQuitSignal()) {
|
||||
process->terminate();
|
||||
logger.info() << "script has been terminated due to quit signal";
|
||||
break;
|
||||
}
|
||||
if (coreParams.testMode) {
|
||||
time.step(delta);
|
||||
} else {
|
||||
auto now = system_clock::now();
|
||||
time.update(
|
||||
duration_cast<microseconds>(now - startupTime).count() / 1e6);
|
||||
delta = time.getDelta();
|
||||
}
|
||||
process->update();
|
||||
if (controller) {
|
||||
controller->getLevel()->getWorld()->updateTimers(delta);
|
||||
controller->update(glm::min(delta, 0.2), false);
|
||||
}
|
||||
engine.postUpdate();
|
||||
|
||||
if (!coreParams.testMode) {
|
||||
auto end = system_clock::now();
|
||||
platform::sleep(targetDelta * 1000 -
|
||||
duration_cast<microseconds>(end - begin).count() / 1000);
|
||||
begin = system_clock::now();
|
||||
}
|
||||
}
|
||||
logger.info() << "test finished";
|
||||
}
|
||||
|
||||
void ServerMainloop::setLevel(std::unique_ptr<Level> level) {
|
||||
if (level == nullptr) {
|
||||
controller->onWorldQuit();
|
||||
engine.getPaths().setCurrentWorldFolder(fs::path());
|
||||
controller = nullptr;
|
||||
} else {
|
||||
controller = std::make_unique<LevelController>(
|
||||
&engine, std::move(level), nullptr
|
||||
);
|
||||
}
|
||||
}
|
||||
19
src/engine/ServerMainloop.hpp
Normal file
19
src/engine/ServerMainloop.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Level;
|
||||
class LevelController;
|
||||
class Engine;
|
||||
|
||||
class ServerMainloop {
|
||||
Engine& engine;
|
||||
std::unique_ptr<LevelController> controller;
|
||||
public:
|
||||
ServerMainloop(Engine& engine);
|
||||
~ServerMainloop();
|
||||
|
||||
void run();
|
||||
|
||||
void setLevel(std::unique_ptr<Level> level);
|
||||
};
|
||||
35
src/engine/Time.hpp
Normal file
35
src/engine/Time.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class Time {
|
||||
uint64_t frame = 0;
|
||||
double lastTime = 0.0;
|
||||
double delta = 0.0;
|
||||
public:
|
||||
Time() {}
|
||||
|
||||
void update(double currentTime) {
|
||||
frame++;
|
||||
delta = currentTime - lastTime;
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
void step(double delta) {
|
||||
frame++;
|
||||
lastTime += delta;
|
||||
this->delta = delta;
|
||||
}
|
||||
|
||||
void set(double currentTime) {
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
double getDelta() const {
|
||||
return delta;
|
||||
}
|
||||
|
||||
double getTime() const {
|
||||
return lastTime;
|
||||
}
|
||||
};
|
||||
@ -159,9 +159,9 @@ std::optional<WorldInfo> WorldFiles::readWorldInfo() {
|
||||
}
|
||||
|
||||
static void read_resources_data(
|
||||
const Content* content, const dv::value& list, ResourceType type
|
||||
const Content& content, const dv::value& list, ResourceType type
|
||||
) {
|
||||
const auto& indices = content->getIndices(type);
|
||||
const auto& indices = content.getIndices(type);
|
||||
for (size_t i = 0; i < list.size(); i++) {
|
||||
auto& map = list[i];
|
||||
const auto& name = map["name"].asString();
|
||||
@ -174,7 +174,7 @@ static void read_resources_data(
|
||||
}
|
||||
}
|
||||
|
||||
bool WorldFiles::readResourcesData(const Content* content) {
|
||||
bool WorldFiles::readResourcesData(const Content& content) {
|
||||
fs::path file = getResourcesFile();
|
||||
if (!fs::is_regular_file(file)) {
|
||||
logger.warning() << "resources.json does not exists";
|
||||
|
||||
@ -49,7 +49,7 @@ public:
|
||||
void createDirectories();
|
||||
|
||||
std::optional<WorldInfo> readWorldInfo();
|
||||
bool readResourcesData(const Content* content);
|
||||
bool readResourcesData(const Content& content);
|
||||
|
||||
static void createContentIndicesCache(
|
||||
const ContentIndices* indices, dv::value& root
|
||||
|
||||
@ -48,6 +48,18 @@ static std::filesystem::path toCanonic(std::filesystem::path path) {
|
||||
}
|
||||
|
||||
void EnginePaths::prepare() {
|
||||
if (!fs::is_directory(resourcesFolder)) {
|
||||
throw std::runtime_error(
|
||||
resourcesFolder.u8string() + " is not a directory"
|
||||
);
|
||||
}
|
||||
if (!fs::is_directory(userFilesFolder)) {
|
||||
fs::create_directories(userFilesFolder);
|
||||
}
|
||||
|
||||
logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string();
|
||||
logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string();
|
||||
|
||||
auto contentFolder = userFilesFolder / CONTENT_FOLDER;
|
||||
if (!fs::is_directory(contentFolder)) {
|
||||
fs::create_directories(contentFolder);
|
||||
@ -120,7 +132,7 @@ std::filesystem::path EnginePaths::getSettingsFile() const {
|
||||
return userFilesFolder / SETTINGS_FILE;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> EnginePaths::scanForWorlds() {
|
||||
std::vector<std::filesystem::path> EnginePaths::scanForWorlds() const {
|
||||
std::vector<std::filesystem::path> folders;
|
||||
|
||||
auto folder = getWorldsFolder();
|
||||
@ -157,6 +169,10 @@ void EnginePaths::setResourcesFolder(std::filesystem::path folder) {
|
||||
this->resourcesFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setScriptFolder(std::filesystem::path folder) {
|
||||
this->scriptFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setCurrentWorldFolder(std::filesystem::path folder) {
|
||||
this->currentWorldFolder = std::move(folder);
|
||||
}
|
||||
@ -177,7 +193,7 @@ std::tuple<std::string, std::string> EnginePaths::parsePath(std::string_view pat
|
||||
|
||||
std::filesystem::path EnginePaths::resolve(
|
||||
const std::string& path, bool throwErr
|
||||
) {
|
||||
) const {
|
||||
auto [prefix, filename] = EnginePaths::parsePath(path);
|
||||
if (prefix.empty()) {
|
||||
throw files_access_error("no entry point specified");
|
||||
@ -199,7 +215,9 @@ std::filesystem::path EnginePaths::resolve(
|
||||
if (prefix == "export") {
|
||||
return userFilesFolder / EXPORT_FOLDER / fs::u8path(filename);
|
||||
}
|
||||
|
||||
if (prefix == "script" && scriptFolder) {
|
||||
return scriptFolder.value() / fs::u8path(filename);
|
||||
}
|
||||
if (contentPacks) {
|
||||
for (auto& pack : *contentPacks) {
|
||||
if (pack.id == prefix) {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
@ -26,6 +27,8 @@ public:
|
||||
void setResourcesFolder(std::filesystem::path folder);
|
||||
std::filesystem::path getResourcesFolder() const;
|
||||
|
||||
void setScriptFolder(std::filesystem::path folder);
|
||||
|
||||
std::filesystem::path getWorldFolderByName(const std::string& name);
|
||||
std::filesystem::path getWorldsFolder() const;
|
||||
std::filesystem::path getConfigFolder() const;
|
||||
@ -39,9 +42,9 @@ public:
|
||||
|
||||
void setContentPacks(std::vector<ContentPack>* contentPacks);
|
||||
|
||||
std::vector<std::filesystem::path> scanForWorlds();
|
||||
std::vector<std::filesystem::path> scanForWorlds() const;
|
||||
|
||||
std::filesystem::path resolve(const std::string& path, bool throwErr = true);
|
||||
std::filesystem::path resolve(const std::string& path, bool throwErr = true) const;
|
||||
|
||||
static std::tuple<std::string, std::string> parsePath(std::string_view view);
|
||||
|
||||
@ -51,6 +54,7 @@ private:
|
||||
std::filesystem::path userFilesFolder {"."};
|
||||
std::filesystem::path resourcesFolder {"res"};
|
||||
std::filesystem::path currentWorldFolder;
|
||||
std::optional<std::filesystem::path> scriptFolder;
|
||||
std::vector<ContentPack>* contentPacks = nullptr;
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user