Merge branch 'main' into dev

This commit is contained in:
MihailRis 2025-09-30 23:40:02 +03:00
commit 289e0f597c
39 changed files with 658 additions and 233 deletions

117
README.md
View File

@ -5,6 +5,8 @@
- [Download](https://github.com/MihailRis/VoxelCore/releases/latest) | [Скачать](https://github.com/MihailRis/VoxelCore/releases/latest)
- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.28/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.28/doc/ru/main-page.md)
---
## Build project in Linux
### Install libraries
@ -13,13 +15,14 @@
```sh
git clone https://github.com/skypjack/entt.git
cd entt/build
cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=on ..
cd entt
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=ON ..
sudo make install
```
> [!WARNING]
> If you are using ALT Linux, you should not use this EnTT installation method
> If you are using ALT Linux, do **not** use this EnTT installation method.
#### ALT Linux based distros
@ -31,11 +34,11 @@ apt-get install entt-devel libglfw3-devel libGLEW-devel libglm-devel libpng-deve
#### Debian based distros
```sh
sudo apt install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev
sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev
```
> [!TIP]
> CMake missing LUA_INCLUDE_DIR and LUA_LIBRARIES fix:
> CMake missing `LUA_INCLUDE_DIR` and `LUA_LIBRARIES` fix:
>
> ```sh
> sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
@ -45,24 +48,24 @@ sudo apt install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopen
#### RHEL based distros
```sh
sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel libvorbis-devel openal-devel luajit-devel libcurl-devel
sudo dnf install glfw-devel glew-devel glm-devel libpng-devel libvorbis-devel openal-soft-devel luajit-devel libcurl-devel
```
#### Arch based distros
If you use X11
If you use X11:
```sh
sudo pacman -S glfw-x11 glew glm libpng libvorbis openal luajit libcurl
```
If you use Wayland
If you use Wayland:
```sh
sudo pacman -S glfw-wayland glew glm libpng libvorbis openal luajit libcurl
```
And you need entt. In yay you can use
And install EnTT:
```sh
yay -S entt
@ -76,9 +79,14 @@ cd VoxelCore
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
cmake --build . --parallel
```
> [!TIP]
> Use `--parallel` to utilize all CPU cores during build.
---
## Building project in macOS
### Install libraries
@ -88,9 +96,7 @@ brew install glfw3 glew glm libpng libvorbis lua luajit libcurl openal-soft skyp
```
> [!TIP]
> If homebrew for some reason could not install the necessary packages:
> ```lua luajit openal-soft```, then download, install and compile them manually
> (Lua, LuaJIT and OpenAL).
> If Homebrew fails to install `lua`, `luajit`, or `openal-soft`, download, compile, and install them manually.
### Building engine with CMake
@ -100,90 +106,109 @@ cd VoxelCore
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
cmake --build . --parallel
```
---
## Building in Windows
>[!NOTE]
> Requirement:
>
> vcpkg, CMake, Git
> [!NOTE]
> Requirements: **vcpkg**, **CMake**, **Git**, and **Visual Studio** (with C++ tools).
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:/
1. If you have Visual Studio installed, the **VCPKG_ROOT** environment variable is often already set in the **Developer Command Prompt for VS**.
2. Otherwise, install **vcpkg** manually:
```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"
Then set the `VCPKG_ROOT` environment variable and add it to `PATH`:
```powershell
$env:VCPKG_ROOT = "C:\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**
After installing **vcpkg** you can build project:
```PowerShell
> [!TIP]
> For troubleshooting, refer to the official [vcpkg documentation](https://learn.microsoft.com/ru-ru/vcpkg/get_started/get-started?pivots=shell-powershell).
After installing **vcpkg**, build the project:
```powershell
git clone --recursive https://github.com/MihailRis/VoxelCore.git
cd VoxelCore
cmake --preset default-vs-msvc-windows
cmake --build --preset default-vs-msvc-windows
```
> [!NOTE]
> Make sure your `CMakeUserPresets.json` (if used) contains the correct `VCPKG_ROOT` path.
---
## Build using Docker
### Step 0. Install docker on your system
> [!NOTE]
> First, install Docker Engine: [https://docs.docker.com/engine/install](https://docs.docker.com/engine/install)
See <https://docs.docker.com/engine/install>
### On Linux
### Do you have Linux
### Step 1. Build docker container
#### Step 1. Build Docker image
```sh
docker build -t voxel-engine .
```
### Step 2. Build project using the docker container
#### Step 2. Build project inside container
```sh
docker run --rm -it -v$(pwd):/project voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build"
docker run --rm -it -v "$(pwd):/project" voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build --parallel"
```
### Step 3. Run project using the docker container
#### Step 3. Run the application (requires X11 forwarding)
```sh
docker run --rm -it -v$(pwd):/project -v/tmp/.X11-unix:/tmp/.X11-unix -v${XAUTHORITY}:/home/user/.Xauthority:ro -eDISPLAY --network=host voxel-engine ./build/VoxelEngine
docker run --rm -it \
-v "$(pwd):/project" \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v "$XAUTHORITY:/home/user/.Xauthority:ro" \
-e DISPLAY="$DISPLAY" \
--network=host \
voxel-engine ./build/VoxelEngine
```
### Do you have Windows
### On Windows
### Step 1. You need to install VcXsrv
> [!NOTE]
> You need an X server like **VcXsrv** to display the GUI.
### Step 2. Run VcXsrv with the command
#### Step 1. Install and run VcXsrv
Launch with:
```powershell
.\vcxsrv.exe :0 -multiwindow -ac
```
### Step 3. Build docker container
#### Step 2. Build Docker image
```powershell
docker build -t voxel-engine .
```
### Step 4. Build project using the docker container
#### Step 3. Build project
```powershell
docker run --rm -it -v "${PWD}:/project" voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build"
docker run --rm -it -v "${PWD}:/project" voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build --parallel"
```
### Step 5. Run project using the docker container
#### Step 4. Run the application
```powershell
docker run --rm -it -v "${PWD}:/project" -e DISPLAY=host.docker.internal:0.0 --network host voxel-engine ./build/VoxelEngine
docker run --rm -it -v "${PWD}:/project" -e DISPLAY=host.docker.internal:0.0 --network=host voxel-engine ./build/VoxelEngine
```

View File

@ -1,5 +1,5 @@
for i=1,3 do
print(string.format("iteration %s", i + 1))
print(string.format("iteration %s", i))
local text = ""
local complete = false

35
dev/tests/network_udp.lua Normal file
View File

@ -0,0 +1,35 @@
math.randomseed(43172)
for i = 1, 15 do
debug.log(string.format("iteration %s", i))
local complete = false
local server = network.udp_open(8645 + i, function (address, port, data, srv)
debug.log(string.format("server received %s byte(s) from %s:%s", #data, address, port))
srv:send(address, port, "pong")
end)
app.tick()
network.udp_connect("localhost", 8645 + i, function (data)
debug.log(string.format("client received %s byte(s) from server", #data))
complete = true
end, function (socket)
debug.log("udp socket opened")
start_coroutine(function()
debug.log("udp data-sender started")
for k = 1, 15 do
local payload = ""
for j = 1, 16 do
payload = payload .. math.random(0, 9)
end
socket:send(payload)
debug.log(string.format("sent packet %s (%s bytes)", k, #payload))
coroutine.yield()
end
app.sleep_until(function () return complete end, nil, 5)
socket:close()
end, "udp-data-sender")
end)
app.sleep_until(function () return complete end, nil, 5)
server:close()
end

View File

@ -86,9 +86,9 @@ body:get_size() -> vec3
body:set_size(size: vec3)
-- Returns the gravity multiplier
body:get_gravity_scale() -> vec3
body:get_gravity_scale() -> number
-- Sets the gravity multiplier
body:set_gravity_scale(scale: vec3)
body:set_gravity_scale(scale: number)
-- Returns the linear velocity attenuation multiplier (used to simulate air resistance and friction)
body:get_linear_damping() -> number

View File

@ -45,6 +45,7 @@
- [Модуль core:bit_converter](scripting/modules/core_bit_converter.md)
- [Модуль core:data_buffer](scripting/modules/core_data_buffer.md)
- [Модули core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
- [Встроенные компоненты сущностей](scripting/core_components.md)
## Аннотации типов данных

View File

@ -0,0 +1,89 @@
# Встроенные компоненты
## *core:pathfinding*
Компонент для построение путей движения мобов.
```lua
local pathfinding = entity:require_component("core:pathfinding")
-- Устанавливает цель движения, не сбрасывая текущий маршрут
pathfinding.set_target(target: vec3)
-- Возвращает текущую цель движения
pathfinding.get_target() --> vec3
-- Устанавливает высоту преодолимого прыжком препятствия
pathfinding.set_jump_height(height: number)
-- Возвращает текущий построенный маршрут или nil
pathfinding.get_route()
-- Сбрасывает текущий построенный маршрут
pathfinding.reset_route()
-- Возвращает следующую точку маршрута, по текущим координатам.
-- (следует использовать компонент core:mob - функция mob.follow_waypoints)
pathfinding.next_waypoint() --> vec3 или nil
-- Устанавливает интервал перестройки маршрута в тактах обновления.
pathfinding.set_refresh_interval(interval: number)
```
## *core:mob*
Компонент для управления движением (включая полёт) и вращением мобов.
```lua
local mob = entity:require_component("core:mob")
-- Выполняет прыжок с силой jump_force * multiplier
mob.jump([опционально] multiplier: number = 1.0)
-- Вертикальное движение (работает в полёте или в плавании (в будущем))
mob.move_vertical(
-- Скорость вертикального движения
speed: number,
-- Текущая скорость сущности (для минимизации вызовов rigidbody:get_vel())
[опционально] current_velocity
)
-- Горизонтальное движение
mob.go(
-- 2D вектор направления движения
dir: vec2,
-- Множитель скорости
speed_multiplier: number,
-- Бег
sprint: bool,
-- Присядь
crouch: bool,
-- Текущая скорость сущности (для минимизации вызовов rigidbody:get_vel())
[опционально] current_velocity
)
-- Меняет направление взгляда сущности, направляя на указанную точку
mob.look_at(
-- Целевая точка
point: vec3,
-- Менять ли направление всей сущности
change_dir: bool = false
)
-- Движение по построенному маршруту.
-- Если не указан pathfinding, требуется наличие у сущности компонента core:pathfinding
mob.follow_waypoints(
-- Возможная замена компонента pathfinding
[опционально] pathfinding
)
-- Устанавливает направление всей сущности
mob.set_dir(dir: vec3)
-- Проверяет, включён ли режим полёта
mob.is_flight() --> bool
-- Включает/выключает режим полёта
mob.set_flight(flag: bool)
```

View File

@ -86,9 +86,9 @@ body:get_size() -> vec3
body:set_size(size: vec3)
-- Возвращает множитель гравитации
body:get_gravity_scale() -> vec3
body:get_gravity_scale() -> number
-- Устанавливает множитель гравитации
body:set_gravity_scale(scale: vec3)
body:set_gravity_scale(scale: number)
-- Возвращает множитель затухания линейной скорости (используется для имитации сопротивления воздуха и трения)
body:get_linear_damping() -> number

View File

@ -22,6 +22,11 @@
На данный момент существует два вида примитивов: box и rect, а также, part (описывает часть примитива, такую как, например, сторона куба).
> [!NOTE]
> По-умолчанию текстурные координаты определяются размерами примитивов.
> При размерах более 1.0 результат не определён и может измениться в последующих обновлениях.
> Используйте `region` или `region-scale` для ручной настройки.
### Свойства `rect`
- `from` - точка начала примитива. Пример: `from (0,0.5,0.125)`

View File

@ -31,11 +31,11 @@
size='32' margin='0,0,425,64' gravity='bottom-right'
color='#FFFFFF50' hover-color='#FFFFFF10'/>
<panel id='packs_add' pos='485,34' size='440,507' color='0' max-length='455' scrollable='true'>
<panel id='packs_add' pos='485,34' size='440,455' color='0' max-length='455' scrollable='true'>
<!-- content is generated in script -->
</panel>
<panel id='packs_cur' pos='15,34' size='440,507' color='0' max-length='455' scrollable='true'>
<panel id='packs_cur' pos='15,34' size='440,455' color='0' max-length='455' scrollable='true'>
<!-- content is generated in script -->
</panel>
</container>

View File

@ -255,30 +255,33 @@ function check_dependencies(packinfo)
if packinfo.dependencies == nil then
return
end
for i,dep in ipairs(packinfo.dependencies) do
for i, dep in ipairs(packinfo.dependencies) do
local depid, depver = unpack(string.split(dep:sub(2,-1), "@"))
if dep:sub(1,1) == '!' then
if not table.has(packs_all, depid) then
return string.format(
"%s (%s)", gui.str("error.dependency-not-found"), depid
)
end
local dep_pack = pack.get_info(depid);
if not compare_version(depver, dep_pack.version) then
local op, ver = Version.parse(depver);
print(string.format("%s: %s !%s %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, op, ver, depid));
return string.format("%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid);
end
if table.has(packs_installed, packinfo.id) then
table.insert(required, depid)
end
if dep:sub(1,1) ~= '!' then
goto continue
end
if not table.has(packs_all, depid) then
return string.format(
"%s (%s)", gui.str("error.dependency-not-found"), depid
)
end
local dep_pack = pack.get_info(depid);
if not compare_version(depver, dep_pack.version) then
local op, ver = Version.parse(depver)
return string.format(
"%s: %s != %s (%s)",
gui.str("error.dependency-version-not-met"),
dep_pack.version, ver, depid
);
end
if table.has(packs_installed, packinfo.id) then
table.insert(required, depid)
end
::continue::
end
return
end

View File

@ -1,35 +0,0 @@
local Random = {}
local M = 2 ^ 31
local A = 1103515245
local C = 12345
function Random.randint(self)
self._seed = (A * self._seed + C) % M
return self._seed
end
function Random.random(self, a, b)
local num = self:randint() % M / M
if b then
return math.floor(num * (b - a + 1) + a)
elseif a then
return math.floor(num * a + 1)
else
return num
end
end
function Random.seed(self, number)
if type(number) ~= "number" then
error("number expected")
end
self._seed = number
end
return function(seed)
if seed and type(seed) ~= "number" then
error("number expected")
end
return setmetatable({_seed = seed or random.random(M)}, {__index = Random})
end

View File

@ -144,7 +144,11 @@ network.udp_connect = function (address, port, datagramHandler, openCallback)
socket.id = network.__connect_udp(address, port)
_udp_client_datagram_callbacks[socket.id] = datagramHandler
_udp_client_open_callbacks[socket.id] = openCallback
if openCallback then
_udp_client_open_callbacks[socket.id] = function()
openCallback(socket)
end
end
return socket
end
@ -225,8 +229,6 @@ block.__process_register_events = function()
end
end
end
print(type, id, x, y, z)
end
end
@ -256,9 +258,15 @@ network.__process_events = function()
end
elseif etype == DATAGRAM then
if side == ON_CLIENT then
_udp_client_datagram_callbacks[cid](data)
local callback = _udp_client_datagram_callbacks[cid]
if callback then
callback(data)
end
elseif side == ON_SERVER then
_udp_server_callbacks[sid](addr, port, data)
local callback = _udp_server_callbacks[sid]
if callback then
callback(addr, port, data)
end
end
elseif etype == RESPONSE then
if event[2] / 100 == 2 then

View File

@ -51,6 +51,7 @@ end
function move_vertical(speed, vel)
vel = vel or body:get_vel()
speed = speed or 1.0
vel[2] = vel[2] * 0.2 + props.movement_speed * speed * 0.8
body:set_vel(vel)
end
@ -145,6 +146,10 @@ function set_dir(new_dir)
dir = new_dir
end
function get_dir()
return dir
end
function is_flight() return flight end
function set_flight(flag) flight = flag end
@ -154,7 +159,7 @@ local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
function on_physics_update(delta)
local grounded = body:is_grounded()
body:set_vdamping(flight)
body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0})
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
body:set_linear_damping(
(flight or not grounded) and props.air_damping or props.ground_damping
)

View File

@ -25,6 +25,10 @@ function get_route()
return route
end
function reset_route()
route = nil
end
function next_waypoint()
if not route or #route == 0 then
return

View File

@ -4,13 +4,22 @@ local mob = entity:require_component("core:mob")
local cheat_speed_mul = 10.0
local function process_player_inputs(pid, delta)
local function get_player_rotation(pid)
local rx, ry, rz = player.get_rot(pid)
local matrix = mat4.rotate({0, 1, 0}, rx)
mat4.rotate(matrix, {1, 0, 0}, ry, matrix)
mat4.rotate(matrix, {0, 0, 1}, rz, matrix)
return matrix
end
local function process_player_inputs(pid, rot, delta)
if not hud or hud.is_inventory_open() or menu.page ~= "" then
return
end
local cam = cameras.get("core:first-person")
local front = cam:get_front()
local right = cam:get_right()
local front = mat4.mul(rot, {0, 0, -1})
local right = mat4.mul(rot, {1, 0, 0})
front[2] = 0.0
vec3.normalize(front, front)
@ -22,8 +31,6 @@ local function process_player_inputs(pid, delta)
local isback = input.is_active('movement.back')
local isleft = input.is_active('movement.left')
local isright = input.is_active('movement.right')
mob.set_flight(player.is_flight(pid))
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
body:set_crouching(iscrouch)
local vel = body:get_vel()
@ -53,10 +60,19 @@ end
function on_physics_update(delta)
local pid = entity:get_player()
if pid ~= -1 then
if pid == -1 then
return
end
mob.set_flight(player.is_flight(pid))
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
if hud and pid == hud.get_player() then
local pos = tsf:get_pos()
local cam = cameras.get("core:first-person")
process_player_inputs(pid, delta)
mob.look_at(vec3.add(pos, cam:get_front()))
local rot = get_player_rotation(pid)
local front = mat4.mul(rot, {0, 0, -1})
process_player_inputs(pid, rot, delta)
mob.look_at(vec3.add(pos, front))
end
end

View File

@ -669,4 +669,41 @@ end
bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor"
random.Random = require "core:internal/random_generator"
function __vc_create_random_methods(random_methods)
local index = 1
local buffer = nil
local buffer_size = 64
local seed_func = random_methods.seed
local random_func = random_methods.random
function random_methods:bytes(n)
local bytes = Bytearray(n)
for i=1,n do
bytes[i] = self:random(255)
end
return bytes
end
function random_methods:seed(x)
seed_func(self, x)
buffer = nil
end
function random_methods:random(a, b)
if not buffer or index > #buffer then
buffer = random_func(self, buffer_size)
index = 1
end
local value = buffer[index]
if b then
value = math.floor(value * (b - a + 1) + a)
elseif a then
value = math.floor(value * a + 1)
end
index = index + 4
return value
end
return random_methods
end

View File

@ -43,7 +43,7 @@ float calc_shadow(
// TODO: add array textures support
float calc_shadow(vec4 modelPos, vec3 realnormal, float distance) {
#ifdef ENABLE_SHADOWS
float s = pow(abs(cos(u_dayTime * PI2)), 0.25) * u_shadowsOpacity;
float s = u_shadowsOpacity;
vec3 normalOffset = realnormal * (distance > 64.0 ? 0.2 : 0.04);
// as slow as mix(...)

View File

@ -4,7 +4,7 @@
#include <constants>
vec3 pick_sky_color(samplerCube cubemap) {
vec3 skyLightColor = texture(cubemap, vec3(0.8f, 0.01f, 0.4f)).rgb;
vec3 skyLightColor = texture(cubemap, vec3(0.4f, 0.05f, 0.4f)).rgb;
skyLightColor *= SKY_LIGHT_TINT;
skyLightColor = min(vec3(1.0f), skyLightColor * SKY_LIGHT_MUL);
skyLightColor = max(MIN_SKY_LIGHT, skyLightColor);

View File

@ -268,7 +268,11 @@ void main() {
camera_vector, // the camera vector (ray direction of this pixel)
1e12f, // max dist, essentially the scene depth
vec3(0.0f), // scene color, the color of the current pixel being rendered
vec3(u_lightDir.x, pow(u_lightDir.y, 3.0), u_lightDir.z), // light direction
vec3(
u_lightDir.x,
u_lightDir.y,
u_lightDir.z
), // light direction
vec3(40.0*fog), // light intensity, 40 looks nice
PLANET_POS, // position of the planet
PLANET_RADIUS, // radius of the planet in meters

View File

@ -32,7 +32,7 @@ protected:
void goBack(size_t count = 1);
void reset();
int64_t parseSimpleInt(int base);
int64_t parseSimpleInt(int base, size_t maxLength = 0xFFFFFFFF);
dv::value parseNumber(int sign);
dv::value parseNumber();
StringT parseString(CharT chr, bool closeRequired = true);

View File

@ -349,7 +349,10 @@ std::basic_string<CharT> BasicParser<CharT>::parseXmlName() {
}
template <typename CharT>
int64_t BasicParser<CharT>::parseSimpleInt(int base) {
int64_t BasicParser<CharT>::parseSimpleInt(int base, size_t maxLength) {
if (maxLength == 0) return 0;
size_t start = pos;
CharT c = peek();
int index = hexchar2int(c);
if (index == -1 || index >= base) {
@ -357,7 +360,7 @@ int64_t BasicParser<CharT>::parseSimpleInt(int base) {
}
int64_t value = index;
pos++;
while (hasNext()) {
while (hasNext() && pos - start < maxLength) {
c = source[pos];
while (c == '_') {
c = source[++pos];
@ -476,7 +479,7 @@ std::basic_string<CharT> BasicParser<CharT>::parseString(
continue;
}
if (c == 'u' || c == 'x') {
int codepoint = parseSimpleInt(16);
int codepoint = parseSimpleInt(16, c == 'u' ? 4 : 2);
ubyte bytes[4];
int size = util::encode_utf8(codepoint, bytes);
CharT chars[4];

View File

@ -1,3 +1,4 @@
#define VC_ENABLE_REFLECTION
#include "ContentPack.hpp"
#include <algorithm>
@ -146,7 +147,7 @@ ContentPack ContentPack::read(const io::path& folder) {
std::uint8_t op_size = 0;
// Two symbol operators
if (op == ">=" || op == "=>" || op == "<=" || op == "=<") {
if (op == ">=" || op == "<=") {
op_size = 2;
depVerOperator = op;
}
@ -169,7 +170,16 @@ ContentPack ContentPack::read(const io::path& folder) {
}
}
pack.dependencies.push_back({level, depName, depVer, depVerOperator});
VersionOperator versionOperator;
if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) {
pack.dependencies.push_back(
{level, depName, depVer, versionOperator}
);
} else {
throw contentpack_error(
pack.id, folder, "invalid version operator"
);
}
}
}

View File

@ -1,14 +1,15 @@
#pragma once
#include "typedefs.hpp"
#include "content_fwd.hpp"
#include "io/io.hpp"
#include "util/EnumMetadata.hpp"
#include <stdexcept>
#include <string>
#include <vector>
#include <optional>
#include "typedefs.hpp"
#include "content_fwd.hpp"
#include "io/io.hpp"
class EnginePaths;
class contentpack_error : public std::runtime_error {
@ -25,11 +26,19 @@ public:
io::path getFolder() const;
};
enum class DependencyVersionOperator {
enum class VersionOperator {
EQUAL, GREATHER, LESS,
GREATHER_OR_EQUAL, LESS_OR_EQUAL
};
VC_ENUM_METADATA(VersionOperator)
{"=", VersionOperator::EQUAL},
{">", VersionOperator::GREATHER},
{"<", VersionOperator::LESS},
{">=", VersionOperator::GREATHER_OR_EQUAL},
{"<=", VersionOperator::LESS_OR_EQUAL},
VC_ENUM_END
enum class DependencyLevel {
REQUIRED, // dependency must be installed
OPTIONAL, // dependency will be installed if found
@ -41,7 +50,7 @@ struct DependencyPack {
DependencyLevel level;
std::string id;
std::string version;
std::string op;
VersionOperator op;
};
struct ContentPackStats {

View File

@ -26,26 +26,11 @@ Version::Version(const std::string& version) {
if (parts.size() > 2) patch = parts[2];
}
DependencyVersionOperator Version::string_to_operator(const std::string& op) {
if (op == "=")
return DependencyVersionOperator::EQUAL;
else if (op == ">")
return DependencyVersionOperator::GREATHER;
else if (op == "<")
return DependencyVersionOperator::LESS;
else if (op == ">=" || op == "=>")
return DependencyVersionOperator::GREATHER_OR_EQUAL;
else if (op == "<=" || op == "=<")
return DependencyVersionOperator::LESS_OR_EQUAL;
else
return DependencyVersionOperator::EQUAL;
}
bool isNumber(const std::string& s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit);
}
bool Version::matches_pattern(const std::string& version) {
bool Version::matchesPattern(const std::string& version) {
for (char c : version) {
if (!isdigit(c) && c != '.') {
return false;

View File

@ -33,25 +33,22 @@ public:
return !(*this > other);
}
bool process_operator(const std::string& op, const Version& other) const {
auto dep_op = Version::string_to_operator(op);
switch (dep_op) {
case DependencyVersionOperator::EQUAL:
bool processOperator(VersionOperator op, const Version& other) const {
switch (op) {
case VersionOperator::EQUAL:
return *this == other;
case DependencyVersionOperator::GREATHER:
case VersionOperator::GREATHER:
return *this > other;
case DependencyVersionOperator::LESS:
case VersionOperator::LESS:
return *this < other;
case DependencyVersionOperator::LESS_OR_EQUAL:
case VersionOperator::LESS_OR_EQUAL:
return *this <= other;
case DependencyVersionOperator::GREATHER_OR_EQUAL:
case VersionOperator::GREATHER_OR_EQUAL:
return *this >= other;
default:
return false;
}
}
static DependencyVersionOperator string_to_operator(const std::string& op);
static bool matches_pattern(const std::string& version);
static bool matchesPattern(const std::string& version);
};

View File

@ -1,3 +1,4 @@
#define VC_ENABLE_REFLECTION
#include "PacksManager.hpp"
#include <queue>
@ -109,9 +110,9 @@ static bool resolve_dependencies(
auto dep_pack = found -> second;
if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version)
if (Version::matchesPattern(dep.version) && Version::matchesPattern(dep_pack.version)
&& Version(dep_pack.version)
.process_operator(dep.op, Version(dep.version))
.processOperator(dep.op, Version(dep.version))
) {
// dependency pack version meets the required one
continue;
@ -120,7 +121,11 @@ static bool resolve_dependencies(
continue;
} else {
throw contentpack_error(
dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'"
dep.id,
io::path(),
"does not meet required version '" +
VersionOperatorMeta.getNameString(dep.op) + dep.version +
"' of '" + pack->id + "'"
);
}

View File

@ -96,12 +96,16 @@ void Shadows::setup(Shader& shader, const Weather& weather) {
if (shadows) {
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
float shadowsOpacity = 1.0f - cloudsIntensity;
shadowsOpacity *= glm::sqrt(glm::abs(
glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f
));
shader.uniform1i("u_screen", 0);
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
shader.uniform3f("u_sunDir", shadowCamera.front);
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable
shader.uniform1f("u_shadowsOpacity", shadowsOpacity); // TODO: make it configurable
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);

View File

@ -22,7 +22,7 @@ namespace markdown {
Result<wchar_t> process(std::wstring_view source, bool eraseMarkdown);
template <typename CharT>
inline std::basic_string<CharT> escape(std::string_view source) {
inline std::basic_string<CharT> escape(std::basic_string_view<CharT> source) {
std::basic_stringstream<CharT> ss;
int pos = 0;
while (pos < source.size()) {

View File

@ -54,7 +54,12 @@ static int l_get_gravity_scale(lua::State* L) {
static int l_set_gravity_scale(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
entity->getRigidbody().hitbox.gravityScale = lua::tovec3(L, 2).y;
auto& hitbox = entity->getRigidbody().hitbox;
if (lua::istable(L, 2)) {
hitbox.gravityScale = lua::tovec3(L, 2).y;
} else {
hitbox.gravityScale = lua::tonumber(L, 2);
}
}
return 0;
}

View File

@ -1,8 +1,4 @@
#include <algorithm>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <set>
#define VC_ENABLE_REFLECTION
#include "assets/AssetsLoader.hpp"
#include "content/Content.hpp"
@ -19,6 +15,12 @@
#include "world/World.hpp"
#include "api_lua.hpp"
#include <algorithm>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <set>
using namespace scripting;
static int l_pack_get_folder(lua::State* L) {
@ -114,8 +116,16 @@ static int l_pack_get_info(
default:
throw std::runtime_error("");
}
auto opString = VersionOperatorMeta.getNameString(dpack.op);
lua::pushfstring(L, "%s%s@%s%s", prefix.c_str(), dpack.id.c_str(), dpack.op.c_str(), dpack.version.c_str());
lua::pushfstring(
L,
"%s%s@%s%s",
prefix.c_str(),
dpack.id.c_str(),
(dpack.op == VersionOperator::EQUAL ? "" : opString).c_str(),
dpack.version.c_str()
);
lua::rawseti(L, i + 1);
}
lua::setfield(L, "dependencies");

View File

@ -3,6 +3,7 @@
#include <string>
#include <vector>
#include <array>
#include <random>
#include "lua_commons.hpp"
@ -114,4 +115,20 @@ namespace lua {
std::shared_ptr<ImageData> mData;
};
static_assert(!std::is_abstract<LuaCanvas>());
class LuaRandom : public Userdata {
public:
std::mt19937 rng;
explicit LuaRandom(uint64_t seed) : rng(seed) {}
virtual ~LuaRandom() override = default;
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "__vc_Random";
};
static_assert(!std::is_abstract<LuaRandom>());
}

View File

@ -169,5 +169,13 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
auto file = "res:scripts/stdmin.lua";
auto src = io::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
newusertype<LuaRandom>(L);
if (getglobal(L, "random")) {
if (getglobal(L, "__vc_Random")) {
setfield(L, "Random");
}
pop(L);
}
return L;
}

View File

@ -275,6 +275,15 @@ namespace lua {
}
return nullptr;
}
template <class T>
inline T& require_userdata(lua::State* L, int idx) {
if (void* rawptr = lua_touserdata(L, idx)) {
return *static_cast<T*>(rawptr);
}
throw std::runtime_error("invalid 'self' value");
}
template <class T, typename... Args>
inline int newuserdata(lua::State* L, Args&&... args) {
const auto& found = usertypeNames.find(typeid(T));

View File

@ -0,0 +1,56 @@
#include "../lua_custom_types.hpp"
#include "../lua_util.hpp"
#include <chrono>
using namespace lua;
using namespace std::chrono;
static int l_random(lua::State* L) {
std::uniform_int_distribution<> dist(0, std::numeric_limits<int>::max());
auto& rng = require_userdata<LuaRandom>(L, 1).rng;
size_t n = touinteger(L, 2);
createtable(L, n, 0);
for (size_t i = 0; i < n; i++) {
pushnumber(L, dist(rng) / (double)std::numeric_limits<int>::max());
rawseti(L, i + 1);
}
return 1;
}
static int l_seed(lua::State* L) {
require_userdata<LuaRandom>(L, 1).rng = std::mt19937(lua::touinteger(L, 2));
return 0;
}
static int l_meta_meta_call(lua::State* L) {
integer_t seed;
if (lua::isnoneornil(L, 1)) {
seed = system_clock::now().time_since_epoch().count();
} else {
seed = tointeger(L, 1);
}
return newuserdata<LuaRandom>(L, seed);
}
int LuaRandom::createMetatable(lua::State* L) {
createtable(L, 0, 3);
requireglobal(L, "__vc_create_random_methods");
createtable(L, 0, 0);
pushcfunction(L, wrap<l_random>);
setfield(L, "random");
pushcfunction(L, wrap<l_seed>);
setfield(L, "seed");
call(L, 1, 1);
setfield(L, "__index");
createtable(L, 0, 1);
pushcfunction(L, wrap<l_meta_meta_call>);
setfield(L, "__call");
setmetatable(L);
return 1;
}

View File

@ -620,6 +620,26 @@ public:
}
};
static sockaddr_in resolve_address_dgram(const std::string& address, int port) {
sockaddr_in serverAddr{};
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* addrinfo = nullptr;
if (int res = getaddrinfo(
address.c_str(), nullptr, &hints, &addrinfo
)) {
throw std::runtime_error(gai_strerror(res));
}
std::memcpy(&serverAddr, addrinfo->ai_addr, sizeof(sockaddr_in));
serverAddr.sin_port = htons(port);
freeaddrinfo(addrinfo);
return serverAddr;
}
class SocketUdpConnection : public UdpConnection {
u64id_t id;
SOCKET descriptor;
@ -652,13 +672,7 @@ public:
throw std::runtime_error("could not create udp socket");
}
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
if (inet_pton(AF_INET, address.c_str(), &serverAddr.sin_addr) <= 0) {
closesocket(descriptor);
throw std::runtime_error("invalid udp address: " + address);
}
serverAddr.sin_port = htons(port);
sockaddr_in serverAddr = resolve_address_dgram(address, port);
if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
auto err = handle_socket_error("udp connect failed");
@ -683,6 +697,7 @@ public:
while (open) {
int size = recv(descriptor, buffer.data(), buffer.size(), 0);
if (size <= 0) {
logger.error() <<id <<"udp connection " << id << handle_socket_error(" recv error").what();
if (!open) break;
closesocket(descriptor);
state = ConnectionState::CLOSED;
@ -697,11 +712,12 @@ public:
}
int send(const char* buffer, size_t length) override {
int len = sendto(descriptor, buffer, length, 0,
(sockaddr*)&addr, sizeof(addr));
int len = ::send(descriptor, buffer, length, 0);
if (len < 0) {
auto err = handle_socket_error(" send failed");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << "udp connection " << id << err.what();
} else totalUpload += len;
return len;
@ -710,6 +726,7 @@ public:
void close(bool discardAll=false) override {
if (!open) return;
open = false;
logger.info() << "closing udp connection "<< id;
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
@ -789,13 +806,11 @@ public:
}
void sendTo(const std::string& addr, int port, const char* buffer, size_t length) override {
sockaddr_in client{};
client.sin_family = AF_INET;
inet_pton(AF_INET, addr.c_str(), &client.sin_addr);
client.sin_port = htons(port);
sendto(descriptor, buffer, length, 0,
reinterpret_cast<sockaddr*>(&client), sizeof(client));
sockaddr_in client = resolve_address_dgram(addr, port);
if (sendto(descriptor, buffer, length, 0,
reinterpret_cast<sockaddr*>(&client), sizeof(client)) < 0) {
logger.error() << handle_socket_error("sendto").what();
}
}
void close() override {

View File

@ -1,6 +1,9 @@
#include "command_line.hpp"
#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include "io/engine_paths.hpp"
#include "util/ArgsReader.hpp"
@ -8,45 +11,69 @@
namespace fs = std::filesystem;
class ArgC {
public:
std::string keyword;
std::function<bool()> execute;
std::string help;
ArgC(const std::string& keyword, std::function<bool()> execute, const std::string& help) {
this->keyword = keyword;
this->execute = execute;
this->help = help;
}
};
static bool perform_keyword(
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
) {
if (keyword == "--res") {
params.resFolder = reader.next();
} else if (keyword == "--dir") {
params.userFolder = reader.next();
} else if (keyword == "--project") {
params.projectFolder = reader.next();
} else if (keyword == "--help" || keyword == "-h") {
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
std::cout << "command-line arguments:\n";
std::cout << " --help - display this help\n";
std::cout << " --version - display engine version\n";
std::cout << " --res <path> - set resources directory\n";
std::cout << " --dir <path> - set userfiles directory\n";
std::cout << " --project <path> - set project directory\n";
std::cout << " --headless - run in headless mode\n";
std::cout << " --test <path> - test script file\n";
std::cout << " --script <path> - main script file\n";
std::cout << std::endl;
return false;
} else if (keyword == "--version") {
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
} else if (keyword == "--headless") {
params.headless = true;
} else if (keyword == "--test") {
auto token = reader.next();
params.testMode = true;
params.scriptFile = token;
} else if (keyword == "--script") {
auto token = reader.next();
params.testMode = false;
params.scriptFile = token;
} else {
throw std::runtime_error("unknown argument " + keyword);
static const std::vector<ArgC> argumentsCommandline = {
ArgC("--res", [&params, &reader]() -> bool {
params.resFolder = reader.next();
return true;
}, "<path> - set resources directory."),
ArgC("--dir", [&params, &reader]() -> bool {
params.userFolder = reader.next();
return true;
}, "<path> - set userfiles directory."),
ArgC("--project", [&params, &reader]() -> bool {
params.projectFolder = reader.next();
return true;
}, "<path> - set project directory."),
ArgC("--test", [&params, &reader]() -> bool {
params.testMode = true;
params.scriptFile = reader.next();
return true;
}, "<path> - test script file."),
ArgC("--script", [&params, &reader]() -> bool {
params.testMode = false;
params.scriptFile = reader.next();
return true;
}, "<path> - main script file."),
ArgC("--headless", [&params]() -> bool {
params.headless = true;
return true;
}, "- run in headless mode."),
ArgC("--version", []() -> bool {
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
}, "- display the engine version."),
ArgC("--help", []() -> bool {
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
std::cout << "Command-line arguments:\n";
for (auto& a : argumentsCommandline) {
std::cout << a.keyword << " " << a.help << std::endl;
}
std::cout << std::endl;
return false;
}, "- display this help.")
};
for (auto& a : argumentsCommandline) {
if (a.keyword == keyword) {
return a.execute();
}
}
return true;
throw std::runtime_error("unknown argument " + keyword);
}
bool parse_cmdline(int argc, char** argv, CoreParameters& params) {

View File

@ -40,7 +40,7 @@ std::string util::escape(std::string_view s, bool escapeUnicode) {
uint cpsize;
int codepoint = decode_utf8(cpsize, s.data() + pos);
if (escapeUnicode) {
ss << "\\u" << std::hex << codepoint;
ss << "\\u" << std::setw(4) << std::setfill('0') << std::hex << codepoint;
} else {
ss << std::string(s.data() + pos, cpsize);
}

View File

@ -19,6 +19,7 @@
static debug::Logger logger("window");
static std::unordered_set<std::string> supported_gl_extensions;
static void window_size_callback(GLFWwindow* window, int width, int height);
static void init_gl_extensions_list() {
GLint numExtensions = 0;
@ -418,7 +419,7 @@ public:
if (fullscreen) {
glfwGetWindowPos(window, &posX, &posY);
glfwSetWindowMonitor(
window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE
window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate
);
} else {
glfwSetWindowMonitor(
@ -430,6 +431,7 @@ public:
settings->height.get(),
GLFW_DONT_CARE
);
window_size_callback(window, settings->width.get(), settings->height.get());
}
double xPos, yPos;
@ -596,6 +598,17 @@ static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) {
handler->input.setCursorPosition(xpos, ypos);
}
static void iconify_callback(GLFWwindow* window, int iconified) {
auto handler = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (handler->isFullscreen() && iconified == 0) {
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(
window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate
);
}
}
static void create_standard_cursors() {
for (int i = 0; i <= static_cast<int>(CursorShape::LAST); i++) {
int cursor = GLFW_ARROW_CURSOR + i;
@ -615,6 +628,7 @@ static void setup_callbacks(GLFWwindow* window) {
glfwSetWindowSizeCallback(window, window_size_callback);
glfwSetCharCallback(window, character_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetWindowIconifyCallback(window, iconify_callback);
}
std::tuple<

View File

@ -1,4 +1,5 @@
#include "util/stringutil.hpp"
#include "coders/BasicParser.hpp"
#include <gtest/gtest.h>
@ -16,6 +17,25 @@ TEST(stringutil, utf8) {
EXPECT_EQ(str, str2);
}
static std::wstring gen_random_unicode_wstring(int n) {
std::wstring str;
str.resize(n);
for (int i = 0; i < n; i++) {
// wstring is 16 bit in some systems
str[i] = rand() & 0xFFFF;
}
return str;
}
TEST(stringutil, utf8_random) {
srand(5436324);
auto str = gen_random_unicode_wstring(10'000);
auto utf8str = util::wstr2str_utf8(str);
auto back = util::str2wstr_utf8(utf8str);
EXPECT_EQ(str, back);
}
TEST(stringutil, base64) {
srand(2019);
for (size_t size = 0; size < 30; size++) {
@ -47,3 +67,37 @@ TEST(stringutil, base64_urlsafe) {
}
}
}
class StringParser : BasicParser<char> {
public:
StringParser(std::string_view source) : BasicParser("<string>", source) {}
std::string parse() {
++pos;
return parseString(source[0], true);
}
};
TEST(stringutil, escape_cases) {
auto escaped = util::escape("тест5", true);
auto expected = "\"\\u0442\\u0435\\u0441\\u04425\"";
ASSERT_EQ(expected, escaped);
srand(345873458);
for (int i = 0; i < 36; i++) {
rand();
}
auto str = gen_random_unicode_wstring(40);
auto utf8str = util::wstr2str_utf8(str);
escaped = util::escape(utf8str, true);
StringParser parser(escaped);
auto restored = parser.parse();
for (int i = 0; i < utf8str.length(); i++) {
if (utf8str[i] != restored[i]) {
std::cout << i << ": " << (int)utf8str[i] << " " << (int)restored[i] << std::endl;
}
}
EXPECT_EQ(utf8str, restored);
}