Merge pull request #673 from MihailRis/move-development-to-main

As part of an experiment with moving the repository to development in the dev branch, I'm temporarily moving it to the main branch.
This commit is contained in:
MihailRis 2025-11-12 20:57:42 +03:00 committed by GitHub
commit b644a76904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
198 changed files with 5716 additions and 2286 deletions

View File

@ -2,7 +2,7 @@ name: x86-64 AppImage
on: on:
push: push:
branches: [ "main", "dev", "release-**"] branches: [ "main", "release-**"]
pull_request: pull_request:
branches: [ "main", "dev" ] branches: [ "main", "dev" ]

View File

@ -2,7 +2,7 @@ name: Macos Build
on: on:
push: push:
branches: [ "main", "dev", "release-**"] branches: [ "main", "release-**"]
pull_request: pull_request:
branches: [ "main", "dev" ] branches: [ "main", "dev" ]

View File

@ -2,7 +2,7 @@ name: Windows Build (CLang)
on: on:
push: push:
branches: [ "main", "dev", "release-**"] branches: [ "main", "release-**"]
pull_request: pull_request:
branches: [ "main", "dev" ] branches: [ "main", "dev" ]

View File

@ -2,7 +2,7 @@ name: MSVC Build
on: on:
push: push:
branches: [ "main", "dev", "release-**"] branches: [ "main", "release-**"]
pull_request: pull_request:
branches: [ "main", "dev" ] branches: [ "main", "dev" ]

View File

@ -203,3 +203,79 @@ audio.count_speakers() -> integer
-- get current number of playing streams -- get current number of playing streams
audio.count_streams() -> integer audio.count_streams() -> integer
``` ```
### audio.PCMStream
```lua
-- Creating a PCM data source
local stream = audio.PCMStream(
-- Sample rate
sample_rate: integer,
-- Number of channels (1 - mono, 2 - stereo)
channels: integer,
-- Number of bits per sample (8 or 16)
bits_per_sample: integer,
)
-- Feeding PCM data into the stream
stream:feed(
-- PCM data to be fed into the stream
data: Bytearray
)
-- Publishing the PCM data source for use by the engine systems
stream:share(
-- Alias of the audio stream, which can be referenced in audio.play_stream
alias: string
)
-- Creating a sound from the PCM data in the stream
stream:create_sound(
-- Name of the created sound
name: string
)
```
### Audio Recording
```lua
-- Requests access to audio recording
-- On confirmation, the callback receives a token for use in audio.input.fetch
audio.input.request_open(callback: function(string))
-- Reads new PCM audio input data
audio.input.fetch(
-- Token obtained through audio.input.request_open
access_token: string,
-- Maximum buffer size in bytes (optional)
[optional] max_read_size: integer
)
```
### Example of Audio Generation:
```lua
-- For working with 16-bit samples, use a U16view over Bytearray
-- Example:
local max_amplitude = 32767
local sample_rate = 44100
local total_samples = sample_rate * 5 -- 5 seconds of mono
local bytes = Bytearray(total_samples * 2) -- 5 seconds of 16-bit mono
local samples = I16view(bytes)
local frequency_hz = 400
for i=1, total_samples do
local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz)
samples[i] = value * max_amplitude
end
local stream_name = "test-stream"
local stream = audio.PCMStream(sample_rate, 1, 16)
stream:feed(bytes)
stream:share(stream_name)
local volume = 1.0
local pitch = 1.0
local channel = "ui"
audio.play_stream_2d(stream_name, volume, pitch, channel)
```

View File

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

View File

@ -7,6 +7,8 @@ The script/test name without the path and extension is available as `app.script`
local filename = "script:"..app.script..".lua" local filename = "script:"..app.script..".lua"
``` ```
Since the control script may not belong to any of the packs, it does not belongs to its own package and has its own global namespace in which all global functions and tables are available, as well as the `app` library.
## Functions ## Functions
```lua ```lua

View File

@ -25,4 +25,12 @@ assets.parse_model(
-- Model name after loading -- Model name after loading
name: str name: str
) )
-- Creates a Canvas from a loaded texture.
assets.to_canvas(
-- The name of the loaded texture.
-- Both standalone textures ("texture_name") and
-- those in an atlas ("atlas:texture_name") are supported
name: str
) --> Canvas
``` ```

View File

@ -20,6 +20,12 @@ hud.open(
[optional] invid: int [optional] invid: int
) -> int ) -> int
-- Returns true if specified layout is open.
hud.is_open(
layoutid: str
) -> bool
-- Open block UI and inventory. -- Open block UI and inventory.
-- Throws an exception if block has no UI layout. -- Throws an exception if block has no UI layout.
-- Returns block inventory ID (if *"inventory-size"=0* a virtual -- Returns block inventory ID (if *"inventory-size"=0* a virtual

View File

@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int}
Returns cursor screen position. Returns cursor screen position.
```lua
input.get_mouse_delta() --> {int, int}
```
Returns cursor movement delta.
```lua ```lua
input.get_bindings() --> strings array input.get_bindings() --> strings array
``` ```

View File

@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
Getter and setter of the property that determines whether the player is loading chunks. Getter and setter of the property that determines whether the player is loading chunks.
```lua
player.get_interaction_distance(playerid: int) -> float
player.set_interaction_distance(playerid: int, distance: float)
```
Getter and setter of the property for max interaction distance.
``` lua ``` lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number) player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number player.get_spawnpoint(playerid: int) -> number, number, number
@ -147,3 +154,21 @@ player.get_entity(playerid: int) -> int
``` ```
Returns unique identifier of the player entity Returns unique identifier of the player entity
```lua
player.get_all_in_radius(center: vec3, radius: number) -> table<int>
```
Returns an array of player IDs within a sphere with center `center` and radius `radius`.
```lua
player.get_all() -> table<int>
```
Returns an array of all active player IDs.
```lua
player.get_nearest(position: vec3) -> int
```
Returns the ID of the player closest to the specified position, or nil if there are no players.

View File

@ -37,6 +37,7 @@ Properties that apply to all elements:
| Name | Type | Read | Write | Description | | Name | Type | Read | Write | Description |
| ------------- | ------- | ---- | ----- | ------------------------------------------- | | ------------- | ------- | ---- | ----- | ------------------------------------------- |
| id | string | yes | *no* | element id | | id | string | yes | *no* | element id |
| exists | bool | yes | *no* | checks if element exists |
| pos | vec2 | yes | yes | element position inside a container | | pos | vec2 | yes | yes | element position inside a container |
| wpos | vec2 | yes | yes | element position inside the window | | wpos | vec2 | yes | yes | element position inside the window |
| size | vec2 | yes | yes | element size | | size | vec2 | yes | yes | element size |
@ -196,6 +197,10 @@ Here, *color* can be specified in the following ways:
| data:update() | applies changes to the canvas and uploads it to the GPU | | data:update() | applies changes to the canvas and uploads it to the GPU |
| data:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) | | data:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) |
| data:create_texture(name: str) | creates and shares texture to renderer | | data:create_texture(name: str) | creates and shares texture to renderer |
| data:unbind_texture() | unbinds the texture from the canvas |
| data:mul(*color* or Canvas) | multiplies a color by the specified color or canvas |
| data:add(*color* or Canvas) | adds a color or another canvas to a color |
| data:sub(*color* or Canvas) | subtracts a color or another canvas to a color |
## Inline frame (iframe) ## Inline frame (iframe)

View File

@ -29,6 +29,7 @@
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса. Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.
Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине. Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
### Звук (Sound) ### Звук (Sound)
Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным. Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.
@ -203,3 +204,79 @@ audio.count_speakers() -> integer
-- получить текущее число проигрываемых аудио-потоков -- получить текущее число проигрываемых аудио-потоков
audio.count_streams() -> integer audio.count_streams() -> integer
``` ```
### audio.PCMStream
```lua
-- создание источника PCM данных
local stream = audio.PCMStream(
-- частота дискретизации
sample_rate: integer,
-- число каналов (1 - моно, 2 - стерео)
channels: integer,
-- число бит на сэмпл (8 или 16)
bits_per_sample: integer,
)
-- подача PCM данных в поток
stream:feed(
-- PCM данные для подачи в поток
data: Bytearray
)
-- публикация источника PCM данных для использования системами движка
stream:share(
-- имя потокового аудио, которое можно будет указать в audio.play_stream
alias: string
)
-- создание звука из имеющихся в потоке PCM данных
stream:create_sound(
-- имя создаваемого звука
name: string
)
```
### Запись звука
```lua
-- запрашивает доступ к записи звука
-- при подтверждении, в callback передаётся токен для использовании в audio.input.fetch
audio.input.request_open(callback: function(string))
-- читает новые PCM данные аудио ввода
audio.input.fetch(
-- токен, полученный через audio.input.request_open
access_token: string,
-- максимальное размер буфера в байтах
[опционально] max_read_size: integer
)
```
### Пример генерации аудио:
```lua
-- для работы с 16-битными семплами используйте U16view поверх Bytearray
-- пример:
local max_amplitude = 32767
local sample_rate = 44100
local total_samples = sample_rate * 5 -- 5 секунд моно
local bytes = Bytearray(total_samples * 2) -- 5 секунд 16 бит моно
local samples = I16view(bytes)
local frequency_hz = 400
for i=1, total_samples do
local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz)
samples[i] = value * max_amplitude
end
local stream_name = "test-stream"
local stream = audio.PCMStream(sample_rate, 1, 16)
stream:feed(bytes)
stream:share(stream_name)
local volume = 1.0
local pitch = 1.0
local channel = "ui"
audio.play_stream_2d(stream_name, volume, pitch, channel)
```

View File

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

View File

@ -7,6 +7,8 @@
local filename = "script:"..app.script..".lua" local filename = "script:"..app.script..".lua"
``` ```
Так как управляющий сценарий может не принадлежать ни одному из паков, он не относиться к своему паку и имеет собственное пространство имён, в котором доступны все глобальные функции и таблицы, а также библиотека `app`.
## Функции ## Функции
```lua ```lua

View File

@ -25,4 +25,12 @@ assets.parse_model(
-- Имя модели после загрузки -- Имя модели после загрузки
name: str name: str
) )
-- Создаёт холст (Canvas) из загруженной текстуры
assets.to_canvas(
-- Имя загруженной текстуры.
-- Поддерживается как отдельные ("имя_текстуры"),
-- так и находящиеся в атласе ("атлас:имя_текстуры").
name: str
) --> Canvas
``` ```

View File

@ -0,0 +1,25 @@
# Библиотека *compression*
Библиотека функций для работы сжатия/разжатия массивов байт
```lua
-- Сжимает массив байт.
compression.encode(
-- Массив байт
data: array of integers,
-- Алгоритм сжатия (поддерживается только gzip)
[опционально] algorithm="gzip",
-- Вернуть результат в table?
[опционально] usetable=false
) -> array of integers
-- Разжимает массив байт.
compression.decode(
-- Массив байт
data: array of integers,
-- Алгоритм разжатия (поддерживается только gzip)
[опционально] algorithm="gzip",
-- Вернуть результат в table?
[опционально] usetable=false
) -> array of integers
```

View File

@ -205,4 +205,4 @@ file.open_named_pipe(имя: str, режим: str) -> io_stream
`/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически. `/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически.
Доступные режимы такие же, как и в `file.open`, за исключением `+` Доступные режимы такие же, как и в `file.open`, за исключением `+`

View File

@ -20,6 +20,10 @@ hud.open(
[опционально] invid: int [опционально] invid: int
) -> int ) -> int
-- Возвращает true если указаный макет UI открыт.
hud.is_open(
layoutid: str
) -> bool
-- Открывает инвентарь и UI блока. -- Открывает инвентарь и UI блока.
-- Если блок не имеет макета UI - бросается исключение. -- Если блок не имеет макета UI - бросается исключение.

View File

@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int}
Возвращает позицию курсора на экране. Возвращает позицию курсора на экране.
```lua
input.get_mouse_delta() --> {int, int}
```
Возращает дельту позиции курсора.
```lua ```lua
input.get_bindings() --> массив строк input.get_bindings() --> массив строк
``` ```

View File

@ -93,6 +93,12 @@ socket:is_connected() --> bool
-- Возвращает адрес и порт соединения. -- Возвращает адрес и порт соединения.
socket:get_address() --> str, int socket:get_address() --> str, int
-- Возвращает состояние NoDelay
socket:is_nodelay() --> bool
-- Устанавливает состояние NoDelay
socket:set_nodelay(state: bool)
``` ```
```lua ```lua

View File

@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг. Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг.
```lua
player.get_interaction_distance(playerid: int) -> float
player.set_interaction_distance(playerid: int, distance: float)
```
Геттер и сеттер свойства, определяющего максимальную дистанцию взаимодействия.
```lua ```lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number) player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number player.get_spawnpoint(playerid: int) -> number, number, number
@ -147,3 +154,21 @@ player.get_entity(playerid: int) -> int
``` ```
Возвращает уникальный идентификатор сущности игрока Возвращает уникальный идентификатор сущности игрока
```lua
player.get_all_in_radius(center: vec3, radius: number) -> table<int>
```
Возвращает массив id игроков в пределах сферы с центром `center` и радиусом `radius`.
```lua
player.get_all() -> table<int>
```
Возвращает массив id всех активных игроков.
```lua
player.get_nearest(position: vec3) -> int
```
Возвращает id ближайшего к указанной позиции игрока, либо nil если игроков нет.

View File

@ -37,6 +37,7 @@ document["worlds-panel"]:clear()
| Название | Тип | Чтение | Запись | Описание | | Название | Тип | Чтение | Запись | Описание |
| ------------- | ------- | ------ | ------ | ----------------------------------------- | | ------------- | ------- | ------ | ------ | ----------------------------------------- |
| id | string | да | *нет* | идентификатор элемента | | id | string | да | *нет* | идентификатор элемента |
| exists | bool | да | *нет* | проверяет, существует ли элемент |
| pos | vec2 | да | да | позиция элемента внутри контейнера | | pos | vec2 | да | да | позиция элемента внутри контейнера |
| wpos | vec2 | да | да | позиция элемента в окне | | wpos | vec2 | да | да | позиция элемента в окне |
| size | vec2 | да | да | размер элемента | | size | vec2 | да | да | размер элемента |
@ -196,6 +197,10 @@ document["worlds-panel"]:clear()
| data:update() | применяет изменения и загружает холст в видеопамять | | data:update() | применяет изменения и загружает холст в видеопамять |
| data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) | | data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | | data:create_texture(name: str) | создаёт и делится текстурой с рендерером |
| data:unbind_texture() | отвязывает текстуру от холста |
| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст |
| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету |
| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету |
## Рамка встраивания (iframe) ## Рамка встраивания (iframe)

View File

@ -42,6 +42,8 @@
- `from` - точка начала примитива. Пример: `from (0,0,0)` - `from` - точка начала примитива. Пример: `from (0,0,0)`
- `to` - противоположная от начала точка. Пример: `to (1,1,1)` - `to` - противоположная от начала точка. Пример: `to (1,1,1)`
- `origin` - точка, относительно которой будет применено вращение. По-умолчанию: центр примитива. Пример: `origin (0.5,0.5,0.5)`
- `rotate` - вращение вокруг осей (x,y,z) в градусах, или кватернион (x,y,z,w). Пример: `rotate (45,0,0)` или `rotate (0.3826834, 0, 0, 0.9238795)`
- `texture` - отображаемая текстура для всех сторон по-умолчанию. - `texture` - отображаемая текстура для всех сторон по-умолчанию.
- `shading` определяет возможность затенения на примитиве. Пример: `shading off` - `shading` определяет возможность затенения на примитиве. Пример: `shading off`
- `delete` удаляет стороны по именам (top, bottom, east, west, north, south) - `delete` удаляет стороны по именам (top, bottom, east, west, north, south)

View File

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

View File

@ -1,18 +1,10 @@
function run_script(path) function run_script(path)
debug.log("starting application script "..path) __vc_start_app_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 end
function refresh() function refresh()
document.list:clear() document.list:clear()
local allpacks = table.merge(pack.get_available(), pack.get_installed()) local allpacks = table.merge(pack.get_available(), pack.get_installed())
local infos = pack.get_info(allpacks) local infos = pack.get_info(allpacks)
for _, name in ipairs(allpacks) do for _, name in ipairs(allpacks) do

View File

@ -24,10 +24,46 @@ function update_setting(x, id, name, postfix)
) )
end end
local initialized = false
function on_open() function on_open()
if not initialized then
initialized = true
local token = audio.input.__get_core_token()
document.root:add("<container id='tm' />")
local prev_amplitude = 0.0
document.tm:setInterval(16, function()
audio.input.fetch(token)
local amplitude = audio.input.get_max_amplitude()
if amplitude > 0.0 then
amplitude = math.sqrt(amplitude)
end
amplitude = math.max(amplitude, prev_amplitude - time.delta())
document.input_volume_inner.size = {
prev_amplitude *
document.input_volume_outer.size[1],
document.input_volume_outer.size[2]
}
prev_amplitude = amplitude
end)
end
create_setting("audio.volume-master", "Master Volume", 0.01) create_setting("audio.volume-master", "Master Volume", 0.01)
create_setting("audio.volume-regular", "Regular Sounds", 0.01) create_setting("audio.volume-regular", "Regular Sounds", 0.01)
create_setting("audio.volume-ui", "UI Sounds", 0.01) create_setting("audio.volume-ui", "UI Sounds", 0.01)
create_setting("audio.volume-ambient", "Ambient", 0.01) create_setting("audio.volume-ambient", "Ambient", 0.01)
create_setting("audio.volume-music", "Music", 0.01) create_setting("audio.volume-music", "Music", 0.01)
document.root:add("<label context='settings'>@Microphone</label>")
document.root:add("<select id='input_device_select' "..
"onselect='function(opt) audio.set_input_device(opt) end'/>")
document.root:add("<container id='input_volume_outer' color='#000000' size='4'>"
.."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>"
.."</container>")
local selectbox = document.input_device_select
local devices = {}
local names = audio.get_input_devices_names()
for i, name in ipairs(names) do
table.insert(devices, {value=name, text=name})
end
selectbox.options = devices
selectbox.value = audio.get_input_info().device_specifier
end end

View File

@ -53,12 +53,22 @@ function create_checkbox(id, name, tooltip)
)) ))
end end
function on_open() function on_open()
create_setting("camera.fov", "FOV", 1, "°") create_setting("camera.fov", "FOV", 1, "°")
create_setting("display.framerate", "Framerate", 1, "", "", true) create_setting("display.framerate", "Framerate", 1, "", "", true)
create_checkbox("display.fullscreen", "Fullscreen")
document.root:add(string.format(
"<select context='settings' onselect='function(opt) core.set_setting(\"display.window-mode\", tonumber(opt)) end' selected='%s'>"..
"<option value='0'>@Windowed</option>"..
"<option value='1'>@Fullscreen</option>"..
"<option value='2'>@Borderless</option>"..
"</select>", core.get_setting("display.window-mode"))
)
create_checkbox("camera.shaking", "Camera Shaking") create_checkbox("camera.shaking", "Camera Shaking")
create_checkbox("camera.inertia", "Camera Inertia") create_checkbox("camera.inertia", "Camera Inertia")
create_checkbox("camera.fov-effects", "Camera FOV Effects") create_checkbox("camera.fov-effects", "Camera FOV Effects")
create_checkbox("display.limit-fps-iconified", "Limit Background FPS") create_checkbox("display.limit-fps-iconified", "Limit Background FPS")
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
end end

View File

@ -39,8 +39,9 @@ function on_open()
create_setting("chunks.load-distance", "Load Distance", 1) create_setting("chunks.load-distance", "Load Distance", 1)
create_setting("chunks.load-speed", "Load Speed", 1) create_setting("chunks.load-speed", "Load Speed", 1)
create_setting("graphics.fog-curve", "Fog Curve", 0.1) create_setting("graphics.fog-curve", "Fog Curve", 0.1)
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip")
create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip")
create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip")
create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip") create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip")
create_checkbox("graphics.ssao", "SSAO", "graphics.ssao.tooltip") create_checkbox("graphics.ssao", "SSAO", "graphics.ssao.tooltip")

View File

@ -1,7 +1,7 @@
<container id="%{id}" size="32" tooltip="%{text}" <container id="%{id}" size="32" tooltip="%{text}"
onclick="events.emit('core:open_traceback', '%{traceback}')"> onclick="events.emit('core:open_traceback', '%{traceback}')">
<image src="gui/%{type}" size="32"/> <image src="gui/%{type}" size="32"/>
<label pos="36,2" sizefunc="-1,-1">%{text}</label> <label pos="36,2" size-func="-1,-1">%{text}</label>
<image src="gui/cross" interactive="true" size="16" gravity="top-right" <image src="gui/cross" interactive="true" size="16" gravity="top-right"
onclick="document['%{id}']:destruct()"></image> onclick="document['%{id}']:destruct()"></image>
</container> </container>

View File

@ -6,7 +6,7 @@
onclick='%{open_func}("%{filename}")' onclick='%{open_func}("%{filename}")'
markup='md' markup='md'
tooltip='%{unit}' tooltip='%{unit}'
sizefunc="-1,-1"> size-func="-1,-1">
[#FFFFFF80]%{path}[#FFFFFFFF]%{name} [#FFFFFF80]%{path}[#FFFFFFFF]%{name}
</label> </label>
</container> </container>

View File

@ -0,0 +1,63 @@
local _base64_encode_urlsafe = base64.encode_urlsafe
local _random_bytes = random.bytes
local core_token = _base64_encode_urlsafe(_random_bytes(18))
local audio_input_tokens_store = {[core_token] = "core"}
audio.input = {}
local _gui_confirm = gui.confirm
local _debug_pack_by_frame = debug.get_pack_by_frame
local _audio_fetch_input = audio.__fetch_input
audio.__fetch_input = nil
local MAX_FETCH = 44100 * 4
local MAX_AMPLITUDE = 32768
local total_fetch = Bytearray()
local max_amplitude = 0.0
function audio.__reset_fetch_buffer()
total_fetch:clear()
max_amplitude = 0.0
end
function audio.input.get_max_amplitude()
return max_amplitude / MAX_AMPLITUDE
end
function audio.input.fetch(token, size)
size = size or MAX_FETCH
if audio_input_tokens_store[token] then
if #total_fetch >= size then
return total_fetch:slice(1, size)
end
local fetched = _audio_fetch_input(size - #total_fetch)
if not fetched then
return
end
for i, sample in ipairs(I16view(fetched)) do
max_amplitude = math.max(math.abs(sample))
end
total_fetch:append(fetched)
return total_fetch:slice()
end
error("access denied")
end
local GRANT_PERMISSION_MSG = "Grant '%{0}' pack audio recording permission?"
function audio.input.request_open(callback)
local token = _base64_encode_urlsafe(_random_bytes(18))
local caller = _debug_pack_by_frame(1)
_gui_confirm(gui.str(GRANT_PERMISSION_MSG):gsub("%%{0}", caller), function()
audio_input_tokens_store[token] = caller
callback(token)
menu:reset()
end)
end
function audio.input.__get_core_token()
local caller = _debug_pack_by_frame(1)
if caller == "core" then
return core_token
end
end

View File

@ -14,6 +14,8 @@ FFI.cdef[[
local malloc = FFI.C.malloc local malloc = FFI.C.malloc
local free = FFI.C.free local free = FFI.C.free
local FFIBytearray
local bytearray_type
local function grow_buffer(self, elems) local function grow_buffer(self, elems)
local new_capacity = math.ceil(self.capacity / 0.75 + elems) local new_capacity = math.ceil(self.capacity / 0.75 + elems)
@ -119,6 +121,23 @@ local function get_capacity(self)
return self.capacity return self.capacity
end end
local function slice(self, offset, length)
offset = offset or 1
length = length or (self.size - offset + 1)
if offset < 1 or offset > self.size then
return FFIBytearray(0)
end
if offset + length - 1 > self.size then
length = self.size - offset + 1
end
local buffer = malloc(length)
if not buffer then
error("malloc(" .. length .. ") returned NULL")
end
FFI.copy(buffer, self.bytes + (offset - 1), length)
return bytearray_type(buffer, length, length)
end
local bytearray_methods = { local bytearray_methods = {
append=append, append=append,
insert=insert, insert=insert,
@ -127,6 +146,7 @@ local bytearray_methods = {
clear=clear, clear=clear,
reserve=reserve, reserve=reserve,
get_capacity=get_capacity, get_capacity=get_capacity,
slice=slice,
} }
local bytearray_mt = { local bytearray_mt = {
@ -168,9 +188,9 @@ local bytearray_mt = {
} }
bytearray_mt.__pairs = bytearray_mt.__ipairs bytearray_mt.__pairs = bytearray_mt.__ipairs
local bytearray_type = FFI.metatype("bytearray_t", bytearray_mt) bytearray_type = FFI.metatype("bytearray_t", bytearray_mt)
local FFIBytearray = { FFIBytearray = {
__call = function (self, n) __call = function (self, n)
local t = type(n) local t = type(n)
if t == "string" then if t == "string" then
@ -210,7 +230,59 @@ local function FFIBytearray_as_string(bytes)
end end
end end
local function create_FFIview_class(name, typename, typesize)
local FFIU16view_mt = {
__index = function(self, key)
if key <= 0 or key > self.size then
return
end
return self.ptr[key - 1]
end,
__newindex = function(self, key, value)
if key == self.size + 1 then
return append(self, value)
elseif key <= 0 or key > self.size then
return
end
self.ptr[key - 1] = value
end,
__len = function(self)
return self.size
end,
__tostring = function(self)
return string.format(name .. "[%s]{...}", tonumber(self.size))
end,
__ipairs = function(self)
local i = 0
return function()
i = i + 1
if i <= self.size then
return i, self.ptr[i - 1]
end
end
end
}
return function (bytes)
local ptr = FFI.cast(typename .. "*", bytes.bytes)
local x = setmetatable({
bytes=bytes,
ptr=ptr,
size=math.floor(bytes.size / typesize),
}, FFIU16view_mt)
return x
end
end
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2)
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2)
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4)
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4)
return { return {
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
FFIBytearray_as_string = FFIBytearray_as_string FFIBytearray_as_string = FFIBytearray_as_string,
FFIU16view = FFIU16view,
FFII16view = FFII16view,
FFIU32view = FFIU32view,
FFII32view = FFII32view,
} }

View File

@ -0,0 +1,146 @@
local breakpoints = {}
local dbg_steps_mode = false
local dbg_step_into_func = false
local hook_lock = false
local current_func
local current_func_stack_size
local __parse_path = parse_path
local _debug_getinfo = debug.getinfo
local _debug_getlocal = debug.getlocal
local __pause = debug.pause
local __error = error
local __sethook = debug.sethook
-- 'return' hook not called for some functions
-- todo: speedup
local function calc_stack_size()
local s = debug.traceback("", 2)
local count = 0
for i in s:gmatch("\n") do
count = count + 1
end
return count
end
local is_debugging = debug.is_debugging()
if is_debugging then
__sethook(function (e, line)
if e == "return" then
local info = _debug_getinfo(2)
if info.func == current_func then
current_func = nil
end
end
if dbg_steps_mode and not hook_lock then
hook_lock = true
if not dbg_step_into_func then
local func = _debug_getinfo(2).func
if func ~= current_func then
return
end
if current_func_stack_size ~= calc_stack_size() then
return
end
end
current_func = func
__pause("step")
debug.pull_events()
end
hook_lock = false
local bps = breakpoints[line]
if not bps then
return
end
local source = _debug_getinfo(2).source
if not bps[source] then
return
end
current_func = _debug_getinfo(2).func
current_func_stack_size = calc_stack_size()
__pause("breakpoint")
debug.pull_events()
end, "lr")
end
local DBG_EVENT_SET_BREAKPOINT = 1
local DBG_EVENT_RM_BREAKPOINT = 2
local DBG_EVENT_STEP = 3
local DBG_EVENT_STEP_INTO_FUNCTION = 4
local DBG_EVENT_RESUME = 5
local DBG_EVENT_GET_VALUE = 6
local __pull_events = debug.__pull_events
local __sendvalue = debug.__sendvalue
debug.__pull_events = nil
debug.__sendvalue = nil
function debug.get_pack_by_frame(func)
local prefix, _ = __parse_path(_debug_getinfo(func, "S").source)
return prefix
end
function debug.pull_events()
if not is_debugging then
return
end
if not debug.is_debugging() then
is_debugging = false
__sethook()
end
local events = __pull_events()
if not events then
return
end
for i, event in ipairs(events) do
if event[1] == DBG_EVENT_SET_BREAKPOINT then
debug.set_breakpoint(event[2], event[3])
elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
debug.remove_breakpoint(event[2], event[3])
elseif event[1] == DBG_EVENT_STEP then
dbg_steps_mode = true
dbg_step_into_func = false
elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
dbg_steps_mode = true
dbg_step_into_func = true
elseif event[1] == DBG_EVENT_RESUME then
dbg_steps_mode = false
dbg_step_into_func = false
elseif event[1] == DBG_EVENT_GET_VALUE then
local _, value = _debug_getlocal(event[2] + 3, event[3])
for _, key in ipairs(event[4]) do
if value == nil then
value = "error: index nil value"
break
end
value = value[key]
end
__sendvalue(value, event[2], event[3], event[4])
__pause()
end
end
end
function debug.set_breakpoint(source, line)
local bps = breakpoints[line]
if not bps then
bps = {}
breakpoints[line] = bps
end
bps[source] = true
end
function debug.remove_breakpoint(source, line)
local bps = breakpoints[line]
if not bps then
return
end
bps[source] = nil
end
function error(message, level)
if is_debugging then
__pause("exception", message)
end
__error(message, level)
end

View File

@ -0,0 +1,48 @@
-- --------- Deprecated functions ------ --
local function wrap_deprecated(func, name, alternatives)
return function (...)
on_deprecated_call(name, alternatives)
return func(...)
end
end
block_index = wrap_deprecated(block.index, "block_index", "block.index")
block_name = wrap_deprecated(block.name, "block_name", "block.name")
blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count")
is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at")
is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at")
set_block = wrap_deprecated(block.set, "set_block", "block.set")
get_block = wrap_deprecated(block.get, "get_block", "block.get")
get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X")
get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y")
get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z")
get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states")
set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states")
get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation")
set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation")
get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits")
set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits")
function load_script(path, nocache)
on_deprecated_call("load_script", "require or loadstring")
return __load_script(path, nocache)
end
_dofile = dofile
-- Replaces dofile('*/content/packid/*') with load_script('packid:*')
function dofile(path)
on_deprecated_call("dofile", "require or loadstring")
local index = string.find(path, "/content/")
if index then
local newpath = string.sub(path, index+9)
index = string.find(newpath, "/")
if index then
local label = string.sub(newpath, 1, index-1)
newpath = label..':'..string.sub(newpath, index+1)
if file.isfile(newpath) then
return __load_script(newpath, true)
end
end
end
return _dofile(path)
end

View File

@ -2,7 +2,14 @@ local events = {
handlers = {} handlers = {}
} }
local __parse_path = parse_path
local __pack_is_installed = pack.is_installed
function events.on(event, func) function events.on(event, func)
local prefix = __parse_path(event)
if prefix ~= "core" and not __pack_is_installed(prefix) then
error("pack prefix required")
end
if events.handlers[event] == nil then if events.handlers[event] == nil then
events.handlers[event] = {} events.handlers[event] = {}
end end

View File

@ -0,0 +1,45 @@
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
function file.parent(path)
local dir = path:match("(.*)/")
if not dir then
return file.prefix(path)..":"
end
return dir
end
function file.path(path)
local pos = path:find(':')
return path:sub(pos + 1)
end
function file.join(a, b)
if a[#a] == ':' then
return a .. b
end
return a .. "/" .. b
end
function file.readlines(path)
local str = file.read(path)
local lines = {}
for s in str:gmatch("[^\r\n]+") do
table.insert(lines, s)
end
return lines
end

View File

@ -0,0 +1,71 @@
function inventory.get_uses(invid, slot)
local uses = inventory.get_data(invid, slot, "uses")
if uses == nil then
return item.uses(inventory.get(invid, slot))
end
return uses
end
function inventory.use(invid, slot)
local itemid, count = inventory.get(invid, slot)
if itemid == nil then
return
end
local item_uses = inventory.get_uses(invid, slot)
if item_uses == nil then
return
end
if item_uses == 1 then
inventory.set(invid, slot, itemid, count - 1)
elseif item_uses > 1 then
inventory.set_data(invid, slot, "uses", item_uses - 1)
end
end
function inventory.decrement(invid, slot, count)
count = count or 1
local itemid, itemcount = inventory.get(invid, slot)
if itemcount <= count then
inventory.set(invid, slot, 0)
else
inventory.set_count(invid, slot, itemcount - count)
end
end
function inventory.get_caption(invid, slot)
local item_id, count = inventory.get(invid, slot)
local caption = inventory.get_data(invid, slot, "caption")
if not caption then return item.caption(item_id) end
return caption
end
function inventory.set_caption(invid, slot, caption)
local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then
return
end
if caption == nil or type(caption) ~= "string" then
caption = ""
end
inventory.set_data(invid, slot, "caption", caption)
end
function inventory.get_description(invid, slot)
local item_id, count = inventory.get(invid, slot)
local description = inventory.get_data(invid, slot, "description")
if not description then return item.description(item_id) end
return description
end
function inventory.set_description(invid, slot, description)
local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then
return
end
if description == nil or type(description) ~= "string" then
description = ""
end
inventory.set_data(invid, slot, "description", description)
end

View File

@ -0,0 +1,37 @@
function math.clamp(_in, low, high)
return math.min(math.max(_in, low), high)
end
function math.rand(low, high)
return low + (high - low) * math.random()
end
function math.normalize(num, conf)
conf = conf or 1
return (num / conf) % 1
end
function math.round(num, places)
places = places or 0
local mult = 10 ^ places
return math.floor(num * mult + 0.5) / mult
end
function math.sum(...)
local numbers = nil
local sum = 0
if type(...) == "table" then
numbers = ...
else
numbers = {...}
end
for _, v in ipairs(numbers) do
sum = sum + v
end
return sum
end

View File

@ -0,0 +1,13 @@
function pack.is_installed(packid)
return file.isfile(packid..":package.json")
end
function pack.data_file(packid, name)
file.mkdirs("world:data/"..packid)
return "world:data/"..packid.."/"..name
end
function pack.shared_file(packid, name)
file.mkdirs("config:"..packid)
return "config:"..packid.."/"..name
end

View File

@ -0,0 +1,126 @@
local pattern_escape_replacements = {
["("] = "%(",
[")"] = "%)",
["."] = "%.",
["%"] = "%%",
["+"] = "%+",
["-"] = "%-",
["*"] = "%*",
["?"] = "%?",
["["] = "%[",
["]"] = "%]",
["^"] = "%^",
["$"] = "%$",
["\0"] = "%z"
}
function string.pattern_safe(str)
return string.gsub(str, ".", pattern_escape_replacements)
end
local string_sub = string.sub
local string_find = string.find
local string_len = string.len
function string.explode(separator, str, withpattern)
if (withpattern == nil) then withpattern = false end
local ret = {}
local current_pos = 1
for i = 1, string_len(str) do
local start_pos, end_pos = string_find(
str, separator, current_pos, not withpattern)
if (not start_pos) then break end
ret[i] = string_sub(str, current_pos, start_pos - 1)
current_pos = end_pos + 1
end
ret[#ret + 1] = string_sub(str, current_pos)
return ret
end
function string.split(str, delimiter)
return string.explode(delimiter, str)
end
function string.formatted_time(seconds, format)
if (not seconds) then seconds = 0 end
local hours = math.floor(seconds / 3600)
local minutes = math.floor((seconds / 60) % 60)
local millisecs = (seconds - math.floor(seconds)) * 1000
seconds = math.floor(seconds % 60)
if (format) then
return string.format(format, minutes, seconds, millisecs)
else
return { h = hours, m = minutes, s = seconds, ms = millisecs }
end
end
function string.replace(str, tofind, toreplace)
local tbl = string.explode(tofind, str)
if (tbl[1]) then return table.concat(tbl, toreplace) end
return str
end
function string.trim(s, char)
if char then char = string.pattern_safe(char) else char = "%s" end
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
end
function string.trim_right(s, char)
if char then char = string.pattern_safe(char) else char = "%s" end
return string.match(s, "^(.-)" .. char .. "*$") or s
end
function string.trim_left(s, char)
if char then char = string.pattern_safe(char) else char = "%s" end
return string.match(s, "^" .. char .. "*(.+)$") or s
end
function string.pad(str, size, char)
char = char == nil and " " or char
local padding = math.floor((size - #str) / 2)
local extra_padding = (size - #str) % 2
return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding)
end
function string.left_pad(str, size, char)
char = char == nil and " " or char
local left_padding = size - #str
return string.rep(char, left_padding) .. str
end
function string.right_pad(str, size, char)
char = char == nil and " " or char
local right_padding = size - #str
return str .. string.rep(char, right_padding)
end
string.lower = utf8.lower
string.upper = utf8.upper
string.escape = utf8.escape
local meta = getmetatable("")
function meta:__index(key)
local val = string[key]
if (val ~= nil) then
return val
elseif (tonumber(key)) then
return string.sub(self, key, key)
end
end
function string.starts_with(str, start)
return string.sub(str, 1, string.len(start)) == start
end
function string.ends_with(str, endStr)
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
end

View File

@ -0,0 +1,179 @@
function table.copy(t)
local copied = {}
for k, v in pairs(t) do
copied[k] = v
end
return copied
end
function table.deep_copy(t)
local copied = {}
for k, v in pairs(t) do
if type(v) == "table" then
copied[k] = table.deep_copy(v)
else
copied[k] = v
end
end
return setmetatable(copied, getmetatable(t))
end
function table.count_pairs(t)
local count = 0
for k, v in pairs(t) do
count = count + 1
end
return count
end
function table.random(t)
return t[math.random(1, #t)]
end
function table.shuffle(t)
for i = #t, 2, -1 do
local j = math.random(i)
t[i], t[j] = t[j], t[i]
end
return t
end
function table.merge(t1, t2)
for i, v in pairs(t2) do
if type(i) == "number" then
t1[#t1 + 1] = v
elseif t1[i] == nil then
t1[i] = v
end
end
return t1
end
function table.map(t, func)
for i, v in pairs(t) do
t[i] = func(i, v)
end
return t
end
function table.filter(t, func)
for i = #t, 1, -1 do
if not func(i, t[i]) then
table.remove(t, i)
end
end
local size = #t
for i, v in pairs(t) do
local i_type = type(i)
if i_type == "number" then
if i < 1 or i > size then
if not func(i, v) then
t[i] = nil
end
end
else
if not func(i, v) then
t[i] = nil
end
end
end
return t
end
function table.set_default(t, key, default)
if t[key] == nil then
t[key] = default
return default
end
return t[key]
end
function table.flat(t)
local flat = {}
for _, v in pairs(t) do
if type(v) == "table" then
table.merge(flat, v)
else
table.insert(flat, v)
end
end
return flat
end
function table.deep_flat(t)
local flat = {}
for _, v in pairs(t) do
if type(v) == "table" then
table.merge(flat, table.deep_flat(v))
else
table.insert(flat, v)
end
end
return flat
end
function table.sub(arr, start, stop)
local res = {}
start = start or 1
stop = stop or #arr
for i = start, stop do
table.insert(res, arr[i])
end
return res
end
function table.has(t, x)
for i,v in ipairs(t) do
if v == x then
return true
end
end
return false
end
function table.index(t, x)
for i,v in ipairs(t) do
if v == x then
return i
end
end
return -1
end
function table.remove_value(t, x)
local index = table.index(t, x)
if index ~= -1 then
table.remove(t, index)
end
end
function table.tostring(t)
local s = '['
for i,v in ipairs(t) do
s = s..tostring(v)
if i < #t then
s = s..', '
end
end
return s..']'
end

View File

@ -0,0 +1,70 @@
local rules = {nexid = 1, rules = {}}
function rules.get_rule(name)
local rule = rules.rules[name]
if rule == nil then
rule = {listeners={}}
rules.rules[name] = rule
end
return rule
end
function rules.get(name)
local rule = rules.rules[name]
if rule == nil then
return nil
end
return rule.value
end
function rules.set(name, value)
local rule = rules.get_rule(name)
rule.value = value
for _, handler in pairs(rule.listeners) do
handler(value)
end
end
function rules.reset(name)
local rule = rules.get_rule(name)
rules.set(rule.default)
end
function rules.listen(name, handler)
local rule = rules.get_rule(name)
local id = rules.nexid
rules.nextid = rules.nexid + 1
rule.listeners[utf8.encode(id)] = handler
return id
end
function rules.create(name, value, handler)
local rule = rules.get_rule(name)
rule.default = value
local handlerid
if handler ~= nil then
handlerid = rules.listen(name, handler)
end
if rules.get(name) == nil then
rules.set(name, value)
elseif handler then
handler(rules.get(name))
end
return handlerid
end
function rules.unlisten(name, id)
local rule = rules.rules[name]
if rule == nil then
return
end
rule.listeners[utf8.encode(id)] = nil
end
function rules.clear()
rules.rules = {}
rules.nextid = 1
end
return rules

View File

@ -44,6 +44,8 @@ local Socket = {__index={
is_alive=function(self) return network.__is_alive(self.id) end, is_alive=function(self) return network.__is_alive(self.id) end,
is_connected=function(self) return network.__is_connected(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, get_address=function(self) return network.__get_address(self.id) end,
set_nodelay=function(self, nodelay) return network.__set_nodelay(self.id, nodelay or false) end,
is_nodelay=function(self) return network.__is_nodelay(self.id) end,
}} }}
local WriteableSocket = {__index={ local WriteableSocket = {__index={

View File

@ -105,82 +105,10 @@ elseif __vc_app then
complete_app_lib(__vc_app) complete_app_lib(__vc_app)
end end
function inventory.get_uses(invid, slot) require "core:internal/maths_inline"
local uses = inventory.get_data(invid, slot, "uses") require "core:internal/debugging"
if uses == nil then require "core:internal/audio_input"
return item.uses(inventory.get(invid, slot)) require "core:internal/extensions/inventory"
end
return uses
end
function inventory.use(invid, slot)
local itemid, count = inventory.get(invid, slot)
if itemid == nil then
return
end
local item_uses = inventory.get_uses(invid, slot)
if item_uses == nil then
return
end
if item_uses == 1 then
inventory.set(invid, slot, itemid, count - 1)
elseif item_uses > 1 then
inventory.set_data(invid, slot, "uses", item_uses - 1)
end
end
function inventory.decrement(invid, slot, count)
count = count or 1
local itemid, itemcount = inventory.get(invid, slot)
if itemcount <= count then
inventory.set(invid, slot, 0)
else
inventory.set_count(invid, slot, itemcount - count)
end
end
function inventory.get_caption(invid, slot)
local item_id, count = inventory.get(invid, slot)
local caption = inventory.get_data(invid, slot, "caption")
if not caption then return item.caption(item_id) end
return caption
end
function inventory.set_caption(invid, slot, caption)
local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then
return
end
if caption == nil or type(caption) ~= "string" then
caption = ""
end
inventory.set_data(invid, slot, "caption", caption)
end
function inventory.get_description(invid, slot)
local item_id, count = inventory.get(invid, slot)
local description = inventory.get_data(invid, slot, "description")
if not description then return item.description(item_id) end
return description
end
function inventory.set_description(invid, slot, description)
local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then
return
end
if description == nil or type(description) ~= "string" then
description = ""
end
inventory.set_data(invid, slot, "description", description)
end
if enable_experimental then
require "core:internal/maths_inline"
end
asserts = require "core:internal/asserts" asserts = require "core:internal/asserts"
events = require "core:internal/events" events = require "core:internal/events"
@ -188,6 +116,19 @@ function pack.unload(prefix)
events.remove_by_prefix(prefix) events.remove_by_prefix(prefix)
end end
function __vc_start_app_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
local script_env = setmetatable({app = app or __vc_app}, {__index=_G})
chunk = setfenv(chunk, script_env)
return __vc_start_coroutine(chunk, path)
end
gui_util = require "core:internal/gui_util" gui_util = require "core:internal/gui_util"
Document = gui_util.Document Document = gui_util.Document
@ -283,11 +224,6 @@ entities.get_all = function(uids)
end end
end end
local bytearray = require "core:internal/bytearray"
Bytearray = bytearray.FFIBytearray
Bytearray_as_string = bytearray.FFIBytearray_as_string
Bytearray_construct = function(...) return Bytearray(...) end
__vc_scripts_registry = require "core:internal/scripts_registry" __vc_scripts_registry = require "core:internal/scripts_registry"
file.open = require "core:internal/stream_providers/file" file.open = require "core:internal/stream_providers/file"
@ -307,86 +243,20 @@ else
os.pid = ffi.C.getpid() os.pid = ffi.C.getpid()
end end
ffi = nil
__vc_lock_internal_modules()
math.randomseed(time.uptime() * 1536227939) math.randomseed(time.uptime() * 1536227939)
rules = {nexid = 1, rules = {}} rules = require "core:internal/rules"
local _rules = rules local _rules = rules
function _rules.get_rule(name)
local rule = _rules.rules[name]
if rule == nil then
rule = {listeners={}}
_rules.rules[name] = rule
end
return rule
end
function _rules.get(name)
local rule = _rules.rules[name]
if rule == nil then
return nil
end
return rule.value
end
function _rules.set(name, value)
local rule = _rules.get_rule(name)
rule.value = value
for _, handler in pairs(rule.listeners) do
handler(value)
end
end
function _rules.reset(name)
local rule = _rules.get_rule(name)
_rules.set(rule.default)
end
function _rules.listen(name, handler)
local rule = _rules.get_rule(name)
local id = _rules.nexid
_rules.nextid = _rules.nexid + 1
rule.listeners[utf8.encode(id)] = handler
return id
end
function _rules.create(name, value, handler)
local rule = _rules.get_rule(name)
rule.default = value
local handlerid
if handler ~= nil then
handlerid = _rules.listen(name, handler)
end
if _rules.get(name) == nil then
_rules.set(name, value)
elseif handler then
handler(_rules.get(name))
end
return handlerid
end
function _rules.unlisten(name, id)
local rule = _rules.rules[name]
if rule == nil then
return
end
rule.listeners[utf8.encode(id)] = nil
end
function _rules.clear()
_rules.rules = {}
_rules.nextid = 1
end
function __vc_on_hud_open() function __vc_on_hud_open()
local _hud_is_content_access = hud._is_content_access
local _hud_set_content_access = hud._set_content_access
local _hud_set_debug_cheats = hud._set_debug_cheats
_rules.create("allow-cheats", true) _rules.create("allow-cheats", true)
_rules.create("allow-content-access", hud._is_content_access(), function(value) _rules.create("allow-content-access", _hud_is_content_access(), function(value)
hud._set_content_access(value) _hud_set_content_access(value)
end) end)
_rules.create("allow-flight", true, function(value) _rules.create("allow-flight", true, function(value)
input.set_enabled("player.flight", value) input.set_enabled("player.flight", value)
@ -407,7 +277,7 @@ function __vc_on_hud_open()
input.set_enabled("player.fast_interaction", value) input.set_enabled("player.fast_interaction", value)
end) end)
_rules.create("allow-debug-cheats", true, function(value) _rules.create("allow-debug-cheats", true, function(value)
hud._set_debug_cheats(value) _hud_set_debug_cheats(value)
end) end)
input.add_callback("devtools.console", function() input.add_callback("devtools.console", function()
if menu.page ~= "" then if menu.page ~= "" then
@ -427,7 +297,9 @@ function __vc_on_hud_open()
end) end)
input.add_callback("key:escape", function() input.add_callback("key:escape", function()
if menu.page ~= "" then if menu.page ~= "" then
menu:reset() if not menu:back() then
menu:reset()
end
elseif hud.is_inventory_open() then elseif hud.is_inventory_open() then
hud.close_inventory() hud.close_inventory()
else else
@ -562,6 +434,9 @@ end
local __post_runnables = {} local __post_runnables = {}
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
audio.__reset_fetch_buffer = nil
function __process_post_runnables() function __process_post_runnables()
if #__post_runnables then if #__post_runnables then
for _, func in ipairs(__post_runnables) do for _, func in ipairs(__post_runnables) do
@ -587,6 +462,8 @@ function __process_post_runnables()
__vc_named_coroutines[name] = nil __vc_named_coroutines[name] = nil
end end
fn_audio_reset_fetch_buffer()
debug.pull_events()
network.__process_events() network.__process_events()
block.__process_register_events() block.__process_register_events()
block.__perform_ticks(time.delta()) block.__perform_ticks(time.delta())
@ -605,6 +482,7 @@ local _getinfo = debug.getinfo
for i, name in ipairs(removed_names) do for i, name in ipairs(removed_names) do
debug[name] = nil debug[name] = nil
end end
debug.getinfo = function(lvl, fields) debug.getinfo = function(lvl, fields)
if type(lvl) == "number" then if type(lvl) == "number" then
lvl = lvl + 1 lvl = lvl + 1
@ -614,51 +492,7 @@ debug.getinfo = function(lvl, fields)
return debuginfo return debuginfo
end end
-- --------- Deprecated functions ------ -- require "core:internal/deprecated"
local function wrap_deprecated(func, name, alternatives)
return function (...)
on_deprecated_call(name, alternatives)
return func(...)
end
end
block_index = wrap_deprecated(block.index, "block_index", "block.index") ffi = nil
block_name = wrap_deprecated(block.name, "block_name", "block.name") __vc_lock_internal_modules()
blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count")
is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at")
is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at")
set_block = wrap_deprecated(block.set, "set_block", "block.set")
get_block = wrap_deprecated(block.get, "get_block", "block.get")
get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X")
get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y")
get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z")
get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states")
set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states")
get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation")
set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation")
get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits")
set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits")
function load_script(path, nocache)
on_deprecated_call("load_script", "require or loadstring")
return __load_script(path, nocache)
end
_dofile = dofile
-- Replaces dofile('*/content/packid/*') with load_script('packid:*')
function dofile(path)
on_deprecated_call("dofile", "require or loadstring")
local index = string.find(path, "/content/")
if index then
local newpath = string.sub(path, index+9)
index = string.find(newpath, "/")
if index then
local label = string.sub(newpath, 1, index-1)
newpath = label..':'..string.sub(newpath, index+1)
if file.isfile(newpath) then
return __load_script(newpath, true)
end
end
end
return _dofile(path)
end

View File

@ -1,60 +1,8 @@
-- Lua has no parallelizm, also _set_data does not call any lua functions so
-- may be reused one global ffi buffer per lua_State
local canvas_ffi_buffer
local canvas_ffi_buffer_size = 0
local ipairs_mt_supported = false
for i, _ in ipairs(setmetatable({l={1}}, {
__ipairs=function(self) return ipairs(self.l) end})) do
ipairs_mt_supported = true
end
if not ipairs_mt_supported then
local raw_ipairs = ipairs
ipairs = function(t)
local metatable = getmetatable(t)
if metatable and metatable.__ipairs then
return metatable.__ipairs(t)
end
return raw_ipairs(t)
end
end
function await(co)
local res, err
while coroutine.status(co) ~= 'dead' do
coroutine.yield()
res, err = coroutine.resume(co)
if err then
return res, err
end
end
return res, err
end
local _ffi = ffi local _ffi = ffi
function __vc_Canvas_set_data(self, data) local _debug_getinfo = debug.getinfo
if type(data) == "cdata" then
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
end
local width = self.width
local height = self.height
local size = width * height * 4
if size > canvas_ffi_buffer_size then
canvas_ffi_buffer = _ffi.new(
string.format("unsigned char[%s]", size)
)
canvas_ffi_buffer_size = size
end
for i=0, size - 1 do
canvas_ffi_buffer[i] = data[i + 1]
end
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
end
function crc32(bytes, chksum) function crc32(bytes, chksum)
local chksum = chksum or 0 chksum = chksum or 0
local length = #bytes local length = #bytes
if type(bytes) == "table" then if type(bytes) == "table" then
@ -90,20 +38,59 @@ function parse_path(path)
return string.sub(path, 1, index-1), string.sub(path, index+1, -1) return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
end end
function pack.is_installed(packid) -- Lua has no parallelizm, also _set_data does not call any lua functions so
return file.isfile(packid..":package.json") -- may be reused one global ffi buffer per lua_State
local canvas_ffi_buffer
local canvas_ffi_buffer_size = 0
local _ffi = ffi
function __vc_Canvas_set_data(self, data)
if type(data) == "cdata" then
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
end
local width = self.width
local height = self.height
local size = width * height * 4
if size > canvas_ffi_buffer_size then
canvas_ffi_buffer = _ffi.new(
string.format("unsigned char[%s]", size)
)
canvas_ffi_buffer_size = size
end
for i=0, size - 1 do
canvas_ffi_buffer[i] = data[i + 1]
end
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
end end
function pack.data_file(packid, name) local ipairs_mt_supported = false
file.mkdirs("world:data/"..packid) for i, _ in ipairs(setmetatable({l={1}}, {
return "world:data/"..packid.."/"..name __ipairs=function(self) return ipairs(self.l) end})) do
ipairs_mt_supported = true
end end
function pack.shared_file(packid, name) if not ipairs_mt_supported then
file.mkdirs("config:"..packid) local raw_ipairs = ipairs
return "config:"..packid.."/"..name ipairs = function(t)
local metatable = getmetatable(t)
if metatable and metatable.__ipairs then
return metatable.__ipairs(t)
end
return raw_ipairs(t)
end
end end
function await(co)
local res, err
while coroutine.status(co) ~= 'dead' do
coroutine.yield()
res, err = coroutine.resume(co)
if err then
return res, err
end
end
return res, err
end
function timeit(iters, func, ...) function timeit(iters, func, ...)
local tm = os.clock() local tm = os.clock()
@ -115,366 +102,6 @@ end
---------------------------------------------- ----------------------------------------------
function math.clamp(_in, low, high)
return math.min(math.max(_in, low), high)
end
function math.rand(low, high)
return low + (high - low) * math.random()
end
function math.normalize(num, conf)
conf = conf or 1
return (num / conf) % 1
end
function math.round(num, places)
places = places or 0
local mult = 10 ^ places
return math.floor(num * mult + 0.5) / mult
end
function math.sum(...)
local numbers = nil
local sum = 0
if type(...) == "table" then
numbers = ...
else
numbers = {...}
end
for _, v in ipairs(numbers) do
sum = sum + v
end
return sum
end
----------------------------------------------
function table.copy(t)
local copied = {}
for k, v in pairs(t) do
copied[k] = v
end
return copied
end
function table.deep_copy(t)
local copied = {}
for k, v in pairs(t) do
if type(v) == "table" then
copied[k] = table.deep_copy(v)
else
copied[k] = v
end
end
return setmetatable(copied, getmetatable(t))
end
function table.count_pairs(t)
local count = 0
for k, v in pairs(t) do
count = count + 1
end
return count
end
function table.random(t)
return t[math.random(1, #t)]
end
function table.shuffle(t)
for i = #t, 2, -1 do
local j = math.random(i)
t[i], t[j] = t[j], t[i]
end
return t
end
function table.merge(t1, t2)
for i, v in pairs(t2) do
if type(i) == "number" then
t1[#t1 + 1] = v
elseif t1[i] == nil then
t1[i] = v
end
end
return t1
end
function table.map(t, func)
for i, v in pairs(t) do
t[i] = func(i, v)
end
return t
end
function table.filter(t, func)
for i = #t, 1, -1 do
if not func(i, t[i]) then
table.remove(t, i)
end
end
local size = #t
for i, v in pairs(t) do
local i_type = type(i)
if i_type == "number" then
if i < 1 or i > size then
if not func(i, v) then
t[i] = nil
end
end
else
if not func(i, v) then
t[i] = nil
end
end
end
return t
end
function table.set_default(t, key, default)
if t[key] == nil then
t[key] = default
return default
end
return t[key]
end
function table.flat(t)
local flat = {}
for _, v in pairs(t) do
if type(v) == "table" then
table.merge(flat, v)
else
table.insert(flat, v)
end
end
return flat
end
function table.deep_flat(t)
local flat = {}
for _, v in pairs(t) do
if type(v) == "table" then
table.merge(flat, table.deep_flat(v))
else
table.insert(flat, v)
end
end
return flat
end
function table.sub(arr, start, stop)
local res = {}
start = start or 1
stop = stop or #arr
for i = start, stop do
table.insert(res, arr[i])
end
return res
end
----------------------------------------------
local pattern_escape_replacements = {
["("] = "%(",
[")"] = "%)",
["."] = "%.",
["%"] = "%%",
["+"] = "%+",
["-"] = "%-",
["*"] = "%*",
["?"] = "%?",
["["] = "%[",
["]"] = "%]",
["^"] = "%^",
["$"] = "%$",
["\0"] = "%z"
}
function string.pattern_safe(str)
return string.gsub(str, ".", pattern_escape_replacements)
end
local string_sub = string.sub
local string_find = string.find
local string_len = string.len
function string.explode(separator, str, withpattern)
if (withpattern == nil) then withpattern = false end
local ret = {}
local current_pos = 1
for i = 1, string_len(str) do
local start_pos, end_pos = string_find(
str, separator, current_pos, not withpattern)
if (not start_pos) then break end
ret[i] = string_sub(str, current_pos, start_pos - 1)
current_pos = end_pos + 1
end
ret[#ret + 1] = string_sub(str, current_pos)
return ret
end
function string.split(str, delimiter)
return string.explode(delimiter, str)
end
function string.formatted_time(seconds, format)
if (not seconds) then seconds = 0 end
local hours = math.floor(seconds / 3600)
local minutes = math.floor((seconds / 60) % 60)
local millisecs = (seconds - math.floor(seconds)) * 1000
seconds = math.floor(seconds % 60)
if (format) then
return string.format(format, minutes, seconds, millisecs)
else
return { h = hours, m = minutes, s = seconds, ms = millisecs }
end
end
function string.replace(str, tofind, toreplace)
local tbl = string.explode(tofind, str)
if (tbl[1]) then return table.concat(tbl, toreplace) end
return str
end
function string.trim(s, char)
if char then char = string.pattern_safe(char) else char = "%s" end
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
end
function string.trim_right(s, char)
if char then char = string.pattern_safe(char) else char = "%s" end
return string.match(s, "^(.-)" .. char .. "*$") or s
end
function string.trim_left(s, char)
if char then char = string.pattern_safe(char) else char = "%s" end
return string.match(s, "^" .. char .. "*(.+)$") or s
end
function string.pad(str, size, char)
char = char == nil and " " or char
local padding = math.floor((size - #str) / 2)
local extra_padding = (size - #str) % 2
return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding)
end
function string.left_pad(str, size, char)
char = char == nil and " " or char
local left_padding = size - #str
return string.rep(char, left_padding) .. str
end
function string.right_pad(str, size, char)
char = char == nil and " " or char
local right_padding = size - #str
return str .. string.rep(char, right_padding)
end
string.lower = utf8.lower
string.upper = utf8.upper
string.escape = utf8.escape
local meta = getmetatable("")
function meta:__index(key)
local val = string[key]
if (val ~= nil) then
return val
elseif (tonumber(key)) then
return string.sub(self, key, key)
end
end
function string.starts_with(str, start)
return string.sub(str, 1, string.len(start)) == start
end
function string.ends_with(str, endStr)
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
end
function table.has(t, x)
for i,v in ipairs(t) do
if v == x then
return true
end
end
return false
end
function table.index(t, x)
for i,v in ipairs(t) do
if v == x then
return i
end
end
return -1
end
function table.remove_value(t, x)
local index = table.index(t, x)
if index ~= -1 then
table.remove(t, index)
end
end
function table.tostring(t)
local s = '['
for i,v in ipairs(t) do
s = s..tostring(v)
if i < #t then
s = s..', '
end
end
return s..']'
end
function file.readlines(path)
local str = file.read(path)
local lines = {}
for s in str:gmatch("[^\r\n]+") do
table.insert(lines, s)
end
return lines
end
local _debug_getinfo = debug.getinfo
function debug.count_frames() function debug.count_frames()
local frames = 1 local frames = 1
while true do while true do
@ -603,15 +230,25 @@ function require(path)
return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env) return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env)
end end
function __scripts_cleanup() function __scripts_cleanup(non_reset_packs)
debug.log("cleaning scripts cache") debug.log("cleaning scripts cache")
if #non_reset_packs == 0 then
debug.log("no non-reset packs")
else
debug.log("non-reset packs: "..table.concat(non_reset_packs, ", "))
end
for k, v in pairs(__cached_scripts) do for k, v in pairs(__cached_scripts) do
local packname, _ = parse_path(k) local packname, _ = parse_path(k)
if table.has(non_reset_packs, packname) then
goto continue
end
if packname ~= "core" then if packname ~= "core" then
debug.log("unloaded "..k) debug.log("unloaded "..k)
__cached_scripts[k] = nil __cached_scripts[k] = nil
package.loaded[k] = nil package.loaded[k] = nil
end end
__vc__pack_envs[packname] = nil
::continue::
end end
end end
@ -619,7 +256,7 @@ function __vc__error(msg, frame, n, lastn)
if events then if events then
local frames = debug.get_traceback(1) local frames = debug.get_traceback(1)
events.emit( events.emit(
"core:error", msg, "core:error", msg,
table.sub(frames, 1 + (n or 0), lastn and #frames-lastn) table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
) )
end end
@ -633,42 +270,20 @@ function __vc_warning(msg, detail, n)
end end
end end
function file.name(path) require "core:internal/extensions/pack"
return path:match("([^:/\\]+)$") require "core:internal/extensions/math"
end require "core:internal/extensions/file"
require "core:internal/extensions/table"
require "core:internal/extensions/string"
function file.stem(path) local bytearray = require "core:internal/bytearray"
local name = file.name(path) Bytearray = bytearray.FFIBytearray
return name:match("(.+)%.[^%.]+$") or name Bytearray_as_string = bytearray.FFIBytearray_as_string
end U16view = bytearray.FFIU16view
I16view = bytearray.FFII16view
function file.ext(path) U32view = bytearray.FFIU32view
return path:match("%.([^:/\\]+)$") I32view = bytearray.FFII32view
end Bytearray_construct = function(...) return Bytearray(...) end
function file.prefix(path)
return path:match("^([^:]+)")
end
function file.parent(path)
local dir = path:match("(.*)/")
if not dir then
return file.prefix(path)..":"
end
return dir
end
function file.path(path)
local pos = path:find(':')
return path:sub(pos + 1)
end
function file.join(a, b)
if a[#a] == ':' then
return a .. b
end
return a .. "/" .. b
end
bit.compile = require "core:bitwise/compiler" bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor" bit.execute = require "core:bitwise/executor"

View File

@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдаць дазвол на мады
Error at line %{0}=Памылка ў радку %{0} Error at line %{0}=Памылка ў радку %{0}
Run=Запусціць Run=Запусціць
Filter=Фільтр Filter=Фільтр
Are you sure you want to open the link: =Ці вы ўпэўненыя, што хочаце адкрыць спасылку:
editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць
devtools.traceback=Стэк выклікаў (ад апошняга) devtools.traceback=Стэк выклікаў (ад апошняга)

View File

@ -7,6 +7,7 @@ Back=Zurück
Continue=Weitermachen Continue=Weitermachen
Add=Hinzufügen Add=Hinzufügen
Converting world...=Weltkonvertierung im Gange... Converting world...=Weltkonvertierung im Gange...
Are you sure you want to open the link: =Sind Sie sicher, dass Sie den Link öffnen möchten:
error.pack-not-found=Paket konnte nicht gefunden werden error.pack-not-found=Paket konnte nicht gefunden werden
error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden

View File

@ -20,6 +20,7 @@ devtools.output=Output
graphics.gamma.tooltip=Lighting brightness curve graphics.gamma.tooltip=Lighting brightness curve
graphics.backlight.tooltip=Backlight to prevent total darkness graphics.backlight.tooltip=Backlight to prevent total darkness
graphics.dense-render.tooltip=Enables transparency in blocks like leaves graphics.dense-render.tooltip=Enables transparency in blocks like leaves
graphics.soft-lighting.tooltip=Enables blocks soft lighting
# settings # settings
settings.Controls Search Mode=Search by attached button name settings.Controls Search Mode=Search by attached button name

View File

@ -19,6 +19,7 @@ Problems=Ongelmia
Monitor=Valvonta Monitor=Valvonta
Debug=Virheenkorjaus Debug=Virheenkorjaus
File=Tiedosto File=Tiedosto
Are you sure you want to open the link: =Haluatko varmasti avata linkin:
devtools.traceback=Puhelupino (viimeisestä) devtools.traceback=Puhelupino (viimeisestä)
error.pack-not-found=Pakettia ei löytynyt! error.pack-not-found=Pakettia ei löytynyt!

View File

@ -7,6 +7,7 @@ Back=Powrót
Continue=Kontynuacja Continue=Kontynuacja
Add=Dodać Add=Dodać
Converting world...=Konwersja świata w toku... Converting world...=Konwersja świata w toku...
Are you sure you want to open the link: =Czy na pewno chcesz otworzyć link:
error.pack-not-found=Nie udało się znaleźć pakietu error.pack-not-found=Nie udało się znaleźć pakietu

View File

@ -25,6 +25,8 @@ Grant %{0} pack modification permission?=Выдать разрешение на
Error at line %{0}=Ошибка на строке %{0} Error at line %{0}=Ошибка на строке %{0}
Run=Запустить Run=Запустить
Filter=Фильтр Filter=Фильтр
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
Grant '%{0}' pack audio recording permission?=Выдать паку '%{0}' разрешение на запись звука?
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
devtools.traceback=Стек вызовов (от последнего) devtools.traceback=Стек вызовов (от последнего)
@ -38,7 +40,8 @@ pack.remove-confirm=Удалить весь поставляемый паком/
# Подсказки # Подсказки
graphics.gamma.tooltip=Кривая яркости освещения graphics.gamma.tooltip=Кривая яркости освещения
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья. graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья
graphics.soft-lighting.tooltip=Включает мягкое освещение у блоков
# Меню # Меню
menu.Apply=Применить menu.Apply=Применить
@ -79,6 +82,7 @@ world.delete-confirm=Удалить мир безвозвратно?
settings.Ambient=Фон settings.Ambient=Фон
settings.Backlight=Подсветка settings.Backlight=Подсветка
settings.Dense blocks render=Плотный рендер блоков settings.Dense blocks render=Плотный рендер блоков
settings.Soft lighting=Мягкое освещение
settings.Camera Shaking=Тряска Камеры settings.Camera Shaking=Тряска Камеры
settings.Camera Inertia=Инерция Камеры settings.Camera Inertia=Инерция Камеры
settings.Camera FOV Effects=Эффекты поля зрения settings.Camera FOV Effects=Эффекты поля зрения
@ -102,6 +106,9 @@ settings.Limit Background FPS=Ограничить фоновую частоту
settings.Advanced render=Продвинутый рендер settings.Advanced render=Продвинутый рендер
settings.Shadows quality=Качество теней settings.Shadows quality=Качество теней
settings.Conflict=Найдены возможные конфликты settings.Conflict=Найдены возможные конфликты
settings.Windowed=Оконный
settings.Borderless=Безрамочный
settings.Microphone=Микрофон
# Управление # Управление
chunks.reload=Перезагрузить Чанки chunks.reload=Перезагрузить Чанки

View File

@ -23,6 +23,7 @@ devtools.traceback=Стек викликів (від останнього)
error.pack-not-found=Не вдалося знайти пакет error.pack-not-found=Не вдалося знайти пакет
error.dependency-not-found=Використовувана залежність не знайдена error.dependency-not-found=Використовувана залежність не знайдена
pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)? pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)?
Are you sure you want to open the link: =Ви впевнені, що хочете відкрити посилання:
# Меню # Меню
menu.Apply=Застосувати menu.Apply=Застосувати

View File

@ -24,6 +24,7 @@ Save=Saqlash
Grant %{0} pack modification permission?=%{0} toplamini ozgartirish ruxsatini berilsinmi? Grant %{0} pack modification permission?=%{0} toplamini ozgartirish ruxsatini berilsinmi?
Error at line %{0}=%{0}-qatorida xatolik Error at line %{0}=%{0}-qatorida xatolik
Run=Ishga tushirish Run=Ishga tushirish
Are you sure you want to open the link: =Haqiqatan ham havolani ochmoqchimisiz:
editor.info.tooltip=CTRL+S - Saqlash\nCTRL+R - Ishga tushirish\nCTRL+Z - Bekor qilish\nCTRL+Y - Qayta bajarish editor.info.tooltip=CTRL+S - Saqlash\nCTRL+R - Ishga tushirish\nCTRL+Z - Bekor qilish\nCTRL+Y - Qayta bajarish
devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab) devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab)

View File

@ -91,7 +91,9 @@ target_compile_options(
/wd4245 # conversion from 'int' to 'const size_t', signed/unsigned /wd4245 # conversion from 'int' to 'const size_t', signed/unsigned
# mismatch # mismatch
/wd4100 # unreferenced formal parameter /wd4100 # unreferenced formal parameter
/wd4457 # declaration of 'var' hides function parameter
/wd4458 # declaration of 'var' hides class member /wd4458 # declaration of 'var' hides class member
/wd4459 # declaration of 'var' hides global declaration
/wd4101 # 'var': unreferenced local variable /wd4101 # 'var': unreferenced local variable
/wd4388 # 'token' : signed/unsigned mismatch /wd4388 # 'token' : signed/unsigned mismatch
/wd4018 # '>': signed/unsigned mismatch /wd4018 # '>': signed/unsigned mismatch
@ -101,6 +103,7 @@ target_compile_options(
-Wextra -Wextra
# additional warnings # additional warnings
-Wformat-nonliteral -Wformat-nonliteral
#-Wsign-conversion
-Wcast-align -Wcast-align
-Wpointer-arith -Wpointer-arith
-Wundef -Wundef
@ -108,6 +111,10 @@ target_compile_options(
-Wno-unused-parameter -Wno-unused-parameter
-Wno-sign-compare -Wno-sign-compare
-Wno-unknown-pragmas -Wno-unknown-pragmas
>
$<$<CXX_COMPILER_ID:Clang>:
-Wduplicated-branches
-Wduplicated-cond
>) >)
target_link_options( target_link_options(

View File

@ -9,7 +9,7 @@
#include "content/Content.hpp" #include "content/Content.hpp"
#include "content/ContentPack.hpp" #include "content/ContentPack.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "graphics/core/Texture.hpp" #include "graphics/core/Texture.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"

View File

@ -15,7 +15,7 @@
#include "coders/vec3.hpp" #include "coders/vec3.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "frontend/UiDocument.hpp" #include "frontend/UiDocument.hpp"
#include "graphics/core/Atlas.hpp" #include "graphics/core/Atlas.hpp"
@ -184,7 +184,7 @@ assetload::postfunc assetload::atlas(
if (!append_atlas(builder, file)) continue; if (!append_atlas(builder, file)) continue;
} }
std::set<std::string> names = builder.getNames(); std::set<std::string> names = builder.getNames();
Atlas* atlas = builder.build(2, false).release(); Atlas* atlas = builder.build(ATLAS_EXTRUSION, false).release();
return [=](auto assets) { return [=](auto assets) {
atlas->prepare(); atlas->prepare();
assets->store(std::unique_ptr<Atlas>(atlas), name); assets->store(std::unique_ptr<Atlas>(atlas), name);
@ -501,7 +501,7 @@ static bool load_animation(
} }
if (!append_atlas(builder, file)) continue; if (!append_atlas(builder, file)) continue;
} }
auto srcAtlas = builder.build(2, true); auto srcAtlas = builder.build(ATLAS_EXTRUSION, true);
if (frameList.empty()) { if (frameList.empty()) {
for (const auto& frameName : builder.getNames()) { for (const auto& frameName : builder.getNames()) {
frameList.emplace_back(frameName, 0); frameList.emplace_back(frameName, 0);

View File

@ -5,11 +5,41 @@
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "alutil.hpp" #include "alutil.hpp"
#include "../MemoryPCMStream.hpp"
static debug::Logger logger("al-audio"); static debug::Logger logger("al-audio");
using namespace audio; using namespace audio;
const char* alc_error_to_string(ALCenum error) {
switch (error) {
case ALC_NO_ERROR:
return "no error";
case ALC_INVALID_DEVICE:
return "invalid device handle";
case ALC_INVALID_CONTEXT:
return "invalid context handle";
case ALC_INVALID_ENUM:
return "invalid enum parameter passed to an ALC call";
case ALC_INVALID_VALUE:
return "invalid value parameter passed to an ALC call";
case ALC_OUT_OF_MEMORY:
return "out of memory";
default:
return "unknown ALC error";
}
}
static bool check_alc_errors(ALCdevice* device, const char* context) {
ALCenum error = alcGetError(device);
if (error == ALC_NO_ERROR) {
return false;
}
logger.error() << context << ": " << alc_error_to_string(error) << "("
<< error << ")";
return true;
}
ALSound::ALSound( ALSound::ALSound(
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
) )
@ -37,6 +67,70 @@ std::unique_ptr<Speaker> ALSound::newInstance(int priority, int channel) const {
return speaker; return speaker;
} }
ALInputDevice::ALInputDevice(
ALAudio* al,
ALCdevice* device,
uint channels,
uint bitsPerSample,
uint sampleRate
)
: al(al),
device(device),
channels(channels),
bitsPerSample(bitsPerSample),
sampleRate(sampleRate) {
const ALCchar* deviceName = alcGetString(device, ALC_CAPTURE_DEVICE_SPECIFIER);
if (deviceName) {
deviceSpecifier = std::string(deviceName);
} else {
logger.warning() << "could not retrieve input device specifier";
}
}
ALInputDevice::~ALInputDevice() {
alcCaptureCloseDevice(device);
check_alc_errors(device, "alcCaptureCloseDevice");
}
void ALInputDevice::startCapture() {
alcCaptureStart(device);
check_alc_errors(device, "alcCaptureStart");
}
void ALInputDevice::stopCapture() {
alcCaptureStop(device);
check_alc_errors(device, "alcCaptureStop");
}
uint ALInputDevice::getChannels() const {
return channels;
}
uint ALInputDevice::getSampleRate() const {
return sampleRate;
}
uint ALInputDevice::getBitsPerSample() const {
return bitsPerSample;
}
const std::string& ALInputDevice::getDeviceSpecifier() const {
return deviceSpecifier;
}
size_t ALInputDevice::read(char* buffer, size_t bufferSize) {
ALCint samplesCount = 0;
alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount);
check_alc_errors(device, "alcGetIntegerv(ALC_CAPTURE_SAMPLES)");
size_t samplesRead = std::min<ALCsizei>(
samplesCount, bufferSize / channels / (bitsPerSample >> 3)
);
alcCaptureSamples(device, buffer, samplesRead);
check_alc_errors(device, "alcCaptureSamples");
return samplesRead * channels * (bitsPerSample >> 3);
}
ALStream::ALStream( ALStream::ALStream(
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
) )
@ -81,9 +175,10 @@ std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) { for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
uint free_buffer = al->getFreeBuffer(); uint free_buffer = al->getFreeBuffer();
if (!preloadBuffer(free_buffer, loop)) { if (!preloadBuffer(free_buffer, loop)) {
break; unusedBuffers.push(free_buffer);
} else {
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
} }
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
} }
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel); return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
} }
@ -130,11 +225,11 @@ void ALStream::unqueueBuffers(uint alsource) {
uint ALStream::enqueueBuffers(uint alsource) { uint ALStream::enqueueBuffers(uint alsource) {
uint preloaded = 0; uint preloaded = 0;
if (!unusedBuffers.empty()) { if (!unusedBuffers.empty()) {
uint first_buffer = unusedBuffers.front(); uint firstBuffer = unusedBuffers.front();
if (preloadBuffer(first_buffer, loop)) { if (preloadBuffer(firstBuffer, loop)) {
preloaded++; preloaded++;
unusedBuffers.pop(); unusedBuffers.pop();
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer)); AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
} }
} }
return preloaded; return preloaded;
@ -144,14 +239,14 @@ void ALStream::update(double delta) {
if (this->speaker == 0) { if (this->speaker == 0) {
return; return;
} }
auto p_speaker = audio::get_speaker(this->speaker); auto speaker = audio::get_speaker(this->speaker);
if (p_speaker == nullptr) { if (speaker == nullptr) {
this->speaker = 0; this->speaker = 0;
return; return;
} }
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker); ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
assert(alspeaker != nullptr); assert(alspeaker != nullptr);
if (alspeaker->stopped) { if (alspeaker->manuallyStopped) {
this->speaker = 0; this->speaker = 0;
return; return;
} }
@ -162,11 +257,11 @@ void ALStream::update(double delta) {
uint preloaded = enqueueBuffers(alsource); uint preloaded = enqueueBuffers(alsource);
// alspeaker->stopped is assigned to false at ALSpeaker::play(...) // alspeaker->stopped is assigned to false at ALSpeaker::play(...)
if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive? if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive?
if (preloaded) { if (preloaded) {
p_speaker->play(); speaker->play();
} else { } else if (isStopOnEnd()){
p_speaker->stop(); speaker->stop();
} }
} }
} }
@ -207,6 +302,14 @@ void ALStream::setTime(duration_t time) {
} }
} }
bool ALStream::isStopOnEnd() const {
return stopOnEnd;
}
void ALStream::setStopOnEnd(bool flag) {
stopOnEnd = flag;
}
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel) ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
: al(al), priority(priority), channel(channel), source(source) { : al(al), priority(priority), channel(channel), source(source) {
} }
@ -273,7 +376,7 @@ void ALSpeaker::setLoop(bool loop) {
void ALSpeaker::play() { void ALSpeaker::play() {
paused = false; paused = false;
stopped = false; manuallyStopped = false;
auto p_channel = get_channel(this->channel); auto p_channel = get_channel(this->channel);
AL_CHECK(alSourcef( AL_CHECK(alSourcef(
source, source,
@ -289,7 +392,7 @@ void ALSpeaker::pause() {
} }
void ALSpeaker::stop() { void ALSpeaker::stop() {
stopped = true; manuallyStopped = true;
if (source) { if (source) {
AL_CHECK(alSourceStop(source)); AL_CHECK(alSourceStop(source));
@ -353,6 +456,13 @@ int ALSpeaker::getPriority() const {
return priority; return priority;
} }
bool ALSpeaker::isManuallyStopped() const {
return manuallyStopped;
}
static bool alc_enumeration_ext = false;
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
: device(device), context(context) { : device(device), context(context) {
ALCint size; ALCint size;
@ -365,9 +475,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
maxSources = attrs[i + 1]; maxSources = attrs[i + 1];
} }
} }
auto devices = getAvailableDevices(); auto outputDevices = getOutputDeviceNames();
logger.info() << "devices:"; logger.info() << "output devices:";
for (auto& name : devices) { for (auto& name : outputDevices) {
logger.info() << " " << name;
}
auto inputDevices = getInputDeviceNames();
logger.info() << "input devices:";
for (auto& name : inputDevices) {
logger.info() << " " << name; logger.info() << " " << name;
} }
} }
@ -385,8 +501,10 @@ ALAudio::~ALAudio() {
AL_CHECK(alDeleteBuffers(1, &buffer)); AL_CHECK(alDeleteBuffers(1, &buffer));
} }
AL_CHECK(alcMakeContextCurrent(context)); alcMakeContextCurrent(nullptr);
check_alc_errors(device, "alcMakeContextCurrent");
alcDestroyContext(context); alcDestroyContext(context);
check_alc_errors(device, "alcDestroyContext");
if (!alcCloseDevice(device)) { if (!alcCloseDevice(device)) {
logger.error() << "device not closed!"; logger.error() << "device not closed!";
} }
@ -411,7 +529,71 @@ std::unique_ptr<Stream> ALAudio::openStream(
return std::make_unique<ALStream>(this, stream, keepSource); return std::make_unique<ALStream>(this, stream, keepSource);
} }
std::vector<std::string> ALAudio::getInputDeviceNames() {
std::vector<std::string> devices;
if (!alc_enumeration_ext) {
logger.warning() << "enumeration extension is not available";
return devices;
}
auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
if (deviceList == nullptr) {
logger.warning() << "no input devices found";
return devices;
}
while (*deviceList) {
std::string deviceName(deviceList);
devices.push_back(deviceName);
deviceList += deviceName.length() + 1;
}
return devices;
}
std::vector<std::string> ALAudio::getOutputDeviceNames() {
std::vector<std::string> devices;
if (!alc_enumeration_ext) {
logger.warning() << "enumeration extension is not available";
return devices;
}
auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
if (deviceList == nullptr) {
logger.warning() << "no input devices found";
return devices;
}
while (*deviceList) {
std::string deviceName(deviceList);
devices.push_back(deviceName);
deviceList += deviceName.length() + 1;
}
return devices;
}
std::unique_ptr<InputDevice> ALAudio::openInputDevice(
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
) {
uint bps = bitsPerSample >> 3;
ALCdevice* device = alcCaptureOpenDevice(
deviceName.empty() ? nullptr : deviceName.c_str(),
sampleRate,
AL::to_al_format(channels, bitsPerSample),
sampleRate * channels * bps / 8
);
if (check_alc_errors(device, "alcCaptureOpenDevice"))
return nullptr;
return std::make_unique<ALInputDevice>(
this, device, channels, bitsPerSample, sampleRate
);
}
std::unique_ptr<ALAudio> ALAudio::create() { std::unique_ptr<ALAudio> ALAudio::create() {
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
ALCdevice* device = alcOpenDevice(nullptr); ALCdevice* device = alcOpenDevice(nullptr);
if (device == nullptr) return nullptr; if (device == nullptr) return nullptr;
ALCcontext* context = alcCreateContext(device, nullptr); ALCcontext* context = alcCreateContext(device, nullptr);
@ -468,24 +650,6 @@ void ALAudio::freeBuffer(uint buffer) {
freebuffers.push_back(buffer); freebuffers.push_back(buffer);
} }
std::vector<std::string> ALAudio::getAvailableDevices() const {
std::vector<std::string> devicesVec;
const ALCchar* devices;
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
if (!AL_GET_ERROR()) {
return devicesVec;
}
const char* ptr = devices;
do {
devicesVec.emplace_back(ptr);
ptr += devicesVec.back().size() + 1;
} while (ptr[0]);
return devicesVec;
}
void ALAudio::setListener( void ALAudio::setListener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
) { ) {

View File

@ -58,6 +58,7 @@ namespace audio {
bool keepSource; bool keepSource;
char buffer[BUFFER_SIZE]; char buffer[BUFFER_SIZE];
bool loop = false; bool loop = false;
bool stopOnEnd = false;
bool preloadBuffer(uint buffer, bool loop); bool preloadBuffer(uint buffer, bool loop);
void unqueueBuffers(uint alsource); void unqueueBuffers(uint alsource);
@ -80,6 +81,39 @@ namespace audio {
void setTime(duration_t time) override; void setTime(duration_t time) override;
static inline constexpr uint STREAM_BUFFERS = 3; static inline constexpr uint STREAM_BUFFERS = 3;
bool isStopOnEnd() const override;
void setStopOnEnd(bool stopOnEnd) override;
};
class ALInputDevice : public InputDevice {
public:
ALInputDevice(
ALAudio* al,
ALCdevice* device,
uint channels,
uint bitsPerSample,
uint sampleRate
);
~ALInputDevice() override;
void startCapture() override;
void stopCapture() override;
uint getChannels() const override;
uint getSampleRate() const override;
uint getBitsPerSample() const override;
const std::string& getDeviceSpecifier() const override;
size_t read(char* buffer, size_t bufferSize) override;
private:
ALAudio* al;
ALCdevice* device;
uint channels;
uint bitsPerSample;
uint sampleRate;
std::string deviceSpecifier;
}; };
/// @brief AL source adapter /// @brief AL source adapter
@ -90,7 +124,7 @@ namespace audio {
float volume = 0.0f; float volume = 0.0f;
public: public:
ALStream* stream = nullptr; ALStream* stream = nullptr;
bool stopped = true; bool manuallyStopped = true;
bool paused = false; bool paused = false;
uint source; uint source;
duration_t duration = 0.0f; duration_t duration = 0.0f;
@ -130,6 +164,8 @@ namespace audio {
bool isRelative() const override; bool isRelative() const override;
int getPriority() const override; int getPriority() const override;
bool isManuallyStopped() const override;
}; };
class ALAudio : public Backend { class ALAudio : public Backend {
@ -152,15 +188,24 @@ namespace audio {
void freeSource(uint source); void freeSource(uint source);
void freeBuffer(uint buffer); void freeBuffer(uint buffer);
std::vector<std::string> getAvailableDevices() const;
std::unique_ptr<Sound> createSound( std::unique_ptr<Sound> createSound(
std::shared_ptr<PCM> pcm, bool keepPCM std::shared_ptr<PCM> pcm, bool keepPCM
) override; ) override;
std::unique_ptr<Stream> openStream( std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource std::shared_ptr<PCMStream> stream, bool keepSource
) override; ) override;
std::unique_ptr<InputDevice> openInputDevice(
const std::string& deviceName,
uint sampleRate,
uint channels,
uint bitsPerSample
) override;
std::vector<std::string> getOutputDeviceNames() override;
std::vector<std::string> getInputDeviceNames() override;
void setListener( void setListener(
glm::vec3 position, glm::vec3 position,
glm::vec3 velocity, glm::vec3 velocity,

View File

@ -0,0 +1,67 @@
#include "MemoryPCMStream.hpp"
#include <cstring>
using namespace audio;
MemoryPCMStream::MemoryPCMStream(
uint sampleRate, uint channels, uint bitsPerSample
)
: sampleRate(sampleRate), channels(channels), bitsPerSample(bitsPerSample) {
}
void MemoryPCMStream::feed(util::span<ubyte> bytes) {
buffer.insert(buffer.end(), bytes.begin(), bytes.end());
}
bool MemoryPCMStream::isOpen() const {
return open;
}
void MemoryPCMStream::close() {
open = false;
buffer = {};
}
size_t MemoryPCMStream::read(char* dst, size_t bufferSize) {
if (!open) {
return PCMStream::ERROR;
}
if (buffer.empty()) {
return 0;
}
size_t count = std::min<size_t>(bufferSize, buffer.size());
std::memcpy(dst, buffer.data(), count);
buffer.erase(buffer.begin(), buffer.begin() + count);
return count;
}
size_t MemoryPCMStream::getTotalSamples() const {
return 0;
}
duration_t MemoryPCMStream::getTotalDuration() const {
return 0.0;
}
uint MemoryPCMStream::getChannels() const {
return channels;
}
uint MemoryPCMStream::getSampleRate() const {
return sampleRate;
}
uint MemoryPCMStream::getBitsPerSample() const {
return bitsPerSample;
}
bool MemoryPCMStream::isSeekable() const {
return false;
}
void MemoryPCMStream::seek(size_t position) {}
size_t MemoryPCMStream::available() const {
return buffer.size();
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <vector>
#include "audio.hpp"
#include "util/span.hpp"
namespace audio {
class MemoryPCMStream : public PCMStream {
public:
MemoryPCMStream(uint sampleRate, uint channels, uint bitsPerSample);
void feed(util::span<ubyte> bytes);
bool isOpen() const override;
void close() override;
size_t read(char* buffer, size_t bufferSize) override;
size_t getTotalSamples() const override;
duration_t getTotalDuration() const override;
uint getChannels() const override;
uint getSampleRate() const override;
uint getBitsPerSample() const override;
bool isSeekable() const override;
void seek(size_t position) override;
size_t available() const;
private:
uint sampleRate;
uint channels;
uint bitsPerSample;
bool open = true;
std::vector<ubyte> buffer;
};
}

View File

@ -61,6 +61,13 @@ namespace audio {
void setTime(duration_t time) override { void setTime(duration_t time) override {
} }
bool isStopOnEnd() const override {
return false;
}
void setStopOnEnd(bool stopOnEnd) override {
}
}; };
class NoAudio : public Backend { class NoAudio : public Backend {
@ -71,10 +78,24 @@ namespace audio {
std::unique_ptr<Sound> createSound( std::unique_ptr<Sound> createSound(
std::shared_ptr<PCM> pcm, bool keepPCM std::shared_ptr<PCM> pcm, bool keepPCM
) override; ) override;
std::unique_ptr<Stream> openStream( std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource std::shared_ptr<PCMStream> stream, bool keepSource
) override; ) override;
std::unique_ptr<InputDevice> openInputDevice(
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
) override {
return nullptr;
}
std::vector<std::string> getInputDeviceNames() override {
return {};
}
std::vector<std::string> getOutputDeviceNames() override {
return {};
}
void setListener( void setListener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
) override { ) override {

View File

@ -151,6 +151,8 @@ public:
} }
}; };
static std::unique_ptr<InputDevice> input_device = nullptr;
void audio::initialize(bool enabled, AudioSettings& settings) { void audio::initialize(bool enabled, AudioSettings& settings) {
enabled = enabled && settings.enabled.get(); enabled = enabled && settings.enabled.get();
if (enabled) { if (enabled) {
@ -180,6 +182,15 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
audio::get_channel(channel.name)->setVolume(value * value); audio::get_channel(channel.name)->setVolume(value * value);
}, true)); }, true));
} }
input_device = backend->openInputDevice("", 44100, 1, 16);
if (input_device) {
input_device->startCapture();
}
}
InputDevice* audio::get_input_device() {
return input_device.get();
} }
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) { std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
@ -242,6 +253,38 @@ std::unique_ptr<Stream> audio::open_stream(
return backend->openStream(std::move(stream), keepSource); return backend->openStream(std::move(stream), keepSource);
} }
std::unique_ptr<InputDevice> audio::open_input_device(
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
) {
return backend->openInputDevice(
deviceName, sampleRate, channels, bitsPerSample
);
}
std::vector<std::string> audio::get_input_devices_names() {
return backend->getInputDeviceNames();
}
std::vector<std::string> audio::get_output_devices_names() {
return backend->getOutputDeviceNames();
}
void audio::set_input_device(const std::string& deviceName) {
auto newDevice = backend->openInputDevice(deviceName, 44100, 1, 16);
if (newDevice == nullptr) {
logger.error() << "could not open input device: " << deviceName;
return;
}
if (input_device) {
input_device->stopCapture();
}
input_device = std::move(newDevice);
if (input_device) {
input_device->startCapture();
}
}
void audio::set_listener( void audio::set_listener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
) { ) {
@ -421,8 +464,15 @@ void audio::update(double delta) {
speaker->update(channel); speaker->update(channel);
} }
if (speaker->isStopped()) { if (speaker->isStopped()) {
streams.erase(it->first); auto foundStream = streams.find(it->first);
it = speakers.erase(it); if (foundStream == streams.end() ||
(!speaker->isManuallyStopped() &&
foundStream->second->isStopOnEnd())) {
streams.erase(it->first);
it = speakers.erase(it);
} else {
it++;
}
} else { } else {
it++; it++;
} }
@ -458,6 +508,9 @@ void audio::reset_channel(int index) {
} }
void audio::close() { void audio::close() {
if (input_device) {
input_device->stopCapture();
}
speakers.clear(); speakers.clear();
delete backend; delete backend;
backend = nullptr; backend = nullptr;

View File

@ -24,6 +24,8 @@ namespace audio {
/// @brief streams and important sounds /// @brief streams and important sounds
constexpr inline int PRIORITY_HIGH = 10; constexpr inline int PRIORITY_HIGH = 10;
constexpr inline size_t MAX_INPUT_SAMPLES = 22050;
class Speaker; class Speaker;
/// @brief Audio speaker states /// @brief Audio speaker states
@ -108,6 +110,31 @@ namespace audio {
} }
}; };
class InputDevice {
public:
virtual ~InputDevice() {};
virtual void startCapture() = 0;
virtual void stopCapture() = 0;
/// @brief Get number of audio channels
/// @return 1 if mono, 2 if stereo
virtual uint getChannels() const = 0;
/// @brief Get audio sampling frequency
/// @return number of mono samples per second
virtual uint getSampleRate() const = 0;
/// @brief Get number of bits per mono sample
/// @return 8 or 16
virtual uint getBitsPerSample() const = 0;
/// @brief Read available data to buffer.
/// @return size of data received or PCMStream::ERROR in case of error
virtual size_t read(char* buffer, size_t bufferSize) = 0;
/// @brief Get device specifier string
virtual const std::string& getDeviceSpecifier() const = 0;
};
/// @brief audio::PCMStream is a data source for audio::Stream /// @brief audio::PCMStream is a data source for audio::Stream
class PCMStream { class PCMStream {
public: public:
@ -121,6 +148,10 @@ namespace audio {
/// (always equals bufferSize if seekable and looped) /// (always equals bufferSize if seekable and looped)
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop); virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
/// @brief Read available data to buffer
/// @param buffer destination buffer
/// @param bufferSize destination buffer size
/// @return count of received bytes or PCMStream::ERROR
virtual size_t read(char* buffer, size_t bufferSize) = 0; virtual size_t read(char* buffer, size_t bufferSize) = 0;
/// @brief Close stream /// @brief Close stream
@ -195,6 +226,9 @@ namespace audio {
/// @brief Set playhead to the selected time /// @brief Set playhead to the selected time
/// @param time selected time /// @param time selected time
virtual void setTime(duration_t time) = 0; virtual void setTime(duration_t time) = 0;
virtual bool isStopOnEnd() const = 0;
virtual void setStopOnEnd(bool stopOnEnd) = 0;
}; };
/// @brief Sound is an audio asset that supposed to support many /// @brief Sound is an audio asset that supposed to support many
@ -329,6 +363,8 @@ namespace audio {
inline bool isStopped() const { inline bool isStopped() const {
return getState() == State::stopped; return getState() == State::stopped;
} }
virtual bool isManuallyStopped() const = 0;
}; };
class Backend { class Backend {
@ -341,12 +377,20 @@ namespace audio {
virtual std::unique_ptr<Stream> openStream( virtual std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource std::shared_ptr<PCMStream> stream, bool keepSource
) = 0; ) = 0;
virtual std::unique_ptr<InputDevice> openInputDevice(
const std::string& deviceName,
uint sampleRate,
uint channels,
uint bitsPerSample
) = 0;
virtual void setListener( virtual void setListener(
glm::vec3 position, glm::vec3 position,
glm::vec3 velocity, glm::vec3 velocity,
glm::vec3 lookAt, glm::vec3 lookAt,
glm::vec3 up glm::vec3 up
) = 0; ) = 0;
virtual std::vector<std::string> getInputDeviceNames() = 0;
virtual std::vector<std::string> getOutputDeviceNames() = 0;
virtual void update(double delta) = 0; virtual void update(double delta) = 0;
/// @brief Check if backend is an abstraction that does not internally /// @brief Check if backend is an abstraction that does not internally
@ -402,6 +446,28 @@ namespace audio {
std::shared_ptr<PCMStream> stream, bool keepSource std::shared_ptr<PCMStream> stream, bool keepSource
); );
/// @brief Open audio input device
/// @param sampleRate sample rate
/// @param channels channels count (1 - mono, 2 - stereo)
/// @param bitsPerSample number of bits per sample (8 or 16)
/// @return new InputDevice instance or nullptr
std::unique_ptr<InputDevice> open_input_device(
const std::string& deviceName,
uint sampleRate,
uint channels,
uint bitsPerSample
);
/// @brief Retrieve names of available audio input devices
/// @return list of device names
std::vector<std::string> get_input_devices_names();
/// @brief Retrieve names of available audio output devices
/// @return list of device names
std::vector<std::string> get_output_devices_names();
void set_input_device(const std::string& deviceName);
/// @brief Configure 3D listener /// @brief Configure 3D listener
/// @param position listener position /// @param position listener position
/// @param velocity listener velocity (used for Doppler effect) /// @param velocity listener velocity (used for Doppler effect)
@ -515,6 +581,8 @@ namespace audio {
/// @brief Stop all playing audio in channel, reset channel state /// @brief Stop all playing audio in channel, reset channel state
void reset_channel(int channel); void reset_channel(int channel);
InputDevice* get_input_device();
/// @brief Finalize audio system /// @brief Finalize audio system
void close(); void close();
}; };

View File

@ -6,7 +6,7 @@
#include <utility> #include <utility>
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "coders/json.hpp" #include "coders/json.hpp"

View File

@ -1,6 +1,8 @@
#include "vcm.hpp" #include "vcm.hpp"
#include <iostream> #include <algorithm>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "xml.hpp" #include "xml.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
@ -73,6 +75,29 @@ static void perform_box(const xmlelement& root, model::Model& model) {
auto from = root.attr("from").asVec3(); auto from = root.attr("from").asVec3();
auto to = root.attr("to").asVec3(); auto to = root.attr("to").asVec3();
glm::vec3 origin = (from + to) * 0.5f;
if (root.has("origin")) {
origin = root.attr("origin").asVec3();
}
glm::mat4 tsf(1.0f);
from -= origin;
to -= origin;
tsf = glm::translate(tsf, origin);
if (root.has("rotate")) {
auto text = root.attr("rotate").getText();
if (std::count(text.begin(), text.end(), ',') == 3) {
auto quat = root.attr("rotate").asVec4();
tsf *= glm::mat4_cast(glm::quat(quat.w, quat.x, quat.y, quat.z));
} else {
auto rot = root.attr("rotate").asVec3();
tsf = glm::rotate(tsf, glm::radians(rot.x), glm::vec3(1, 0, 0));
tsf = glm::rotate(tsf, glm::radians(rot.y), glm::vec3(0, 1, 0));
tsf = glm::rotate(tsf, glm::radians(rot.z), glm::vec3(0, 0, 1));
}
}
UVRegion regions[6] {}; UVRegion regions[6] {};
regions[0].scale(to.x - from.x, to.y - from.y); regions[0].scale(to.x - from.x, to.y - from.y);
regions[1].scale(from.x - to.x, to.y - from.y); regions[1].scale(from.x - to.x, to.y - from.y);
@ -142,7 +167,7 @@ static void perform_box(const xmlelement& root, model::Model& model) {
bool enabled[6] {}; bool enabled[6] {};
enabled[i] = true; enabled[i] = true;
auto& mesh = model.addMesh(texfaces[i], shading); auto& mesh = model.addMesh(texfaces[i], shading);
mesh.addBox(center, halfsize, regions, enabled); mesh.addBox(center, halfsize, regions, enabled, tsf);
} }
} }

View File

@ -6,7 +6,7 @@
#include <string> #include <string>
inline constexpr int ENGINE_VERSION_MAJOR = 0; inline constexpr int ENGINE_VERSION_MAJOR = 0;
inline constexpr int ENGINE_VERSION_MINOR = 29; inline constexpr int ENGINE_VERSION_MINOR = 30;
#ifdef NDEBUG #ifdef NDEBUG
inline constexpr bool ENGINE_DEBUG_BUILD = false; inline constexpr bool ENGINE_DEBUG_BUILD = false;
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
inline constexpr bool ENGINE_DEBUG_BUILD = true; inline constexpr bool ENGINE_DEBUG_BUILD = true;
#endif // NDEBUG #endif // NDEBUG
inline const std::string ENGINE_VERSION_STRING = "0.29"; inline const std::string ENGINE_VERSION_STRING = "0.30";
/// @brief world regions format version /// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3; inline constexpr uint REGION_FORMAT_VERSION = 3;
@ -61,6 +61,8 @@ inline constexpr int ITEM_ICON_SIZE = 48;
inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8; inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8;
inline constexpr int ATLAS_EXTRUSION = 2;
inline const std::string SHADERS_FOLDER = "shaders"; inline const std::string SHADERS_FOLDER = "shaders";
inline const std::string TEXTURES_FOLDER = "textures"; inline const std::string TEXTURES_FOLDER = "textures";
inline const std::string FONTS_FOLDER = "fonts"; inline const std::string FONTS_FOLDER = "fonts";

View File

@ -1,7 +1,7 @@
#include "ContentControl.hpp" #include "ContentControl.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "Content.hpp" #include "Content.hpp"
#include "ContentPack.hpp" #include "ContentPack.hpp"
#include "ContentBuilder.hpp" #include "ContentBuilder.hpp"
@ -30,6 +30,7 @@ ContentControl::ContentControl(
manager->setSources({ manager->setSources({
"world:content", "world:content",
"user:content", "user:content",
"project:content",
"res:content", "res:content",
}); });
} }
@ -48,10 +49,10 @@ std::vector<std::string>& ContentControl::getBasePacks() {
return basePacks; return basePacks;
} }
void ContentControl::resetContent() { void ContentControl::resetContent(const std::vector<std::string>& nonReset) {
paths.setCurrentWorldFolder(""); paths.setCurrentWorldFolder("");
scripting::cleanup(); scripting::cleanup(nonReset);
std::vector<PathsRoot> resRoots; std::vector<PathsRoot> resRoots;
{ {
auto pack = ContentPack::createCore(); auto pack = ContentPack::createCore();
@ -78,8 +79,6 @@ void ContentControl::loadContent(const std::vector<std::string>& names) {
} }
void ContentControl::loadContent() { void ContentControl::loadContent() {
scripting::cleanup();
std::vector<std::string> names; std::vector<std::string> names;
for (auto& pack : contentPacks) { for (auto& pack : contentPacks) {
names.push_back(pack.id); names.push_back(pack.id);

View File

@ -34,7 +34,7 @@ public:
std::vector<std::string>& getBasePacks(); std::vector<std::string>& getBasePacks();
/// @brief Reset content to base packs list /// @brief Reset content to base packs list
void resetContent(); void resetContent(const std::vector<std::string>& nonReset);
void loadContent(const std::vector<std::string>& names); void loadContent(const std::vector<std::string>& names);

View File

@ -13,7 +13,7 @@
#include "objects/rigging.hpp" #include "objects/rigging.hpp"
#include "util/listutil.hpp" #include "util/listutil.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
static debug::Logger logger("content-loader"); static debug::Logger logger("content-loader");

View File

@ -8,7 +8,7 @@
#include "coders/json.hpp" #include "coders/json.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "data/dv.hpp" #include "data/dv.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "coders/commons.hpp" #include "coders/commons.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"

View File

@ -6,7 +6,7 @@
#include "../ContentPack.hpp" #include "../ContentPack.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "world/generator/GeneratorDef.hpp" #include "world/generator/GeneratorDef.hpp"

View File

@ -4,7 +4,7 @@
#include "content/Content.hpp" #include "content/Content.hpp"
#include "content/ContentBuilder.hpp" #include "content/ContentBuilder.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "window/input.hpp" #include "window/input.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "coders/toml.hpp" #include "coders/toml.hpp"

View File

@ -0,0 +1,315 @@
#include "DebuggingServer.hpp"
#include "engine/Engine.hpp"
#include "network/Network.hpp"
#include "debug/Logger.hpp"
#include "coders/json.hpp"
using namespace devtools;
static debug::Logger logger("debug-server");
ClientConnection::~ClientConnection() {
if (auto connection = dynamic_cast<network::ReadableConnection*>(
network.getConnection(this->connection, true)
)) {
connection->close();
}
}
bool ClientConnection::initiate(network::ReadableConnection* connection) {
if (connection->available() < 8) {
return false;
}
char buffer[8] {};
char expected[8] {};
std::memcpy(expected, VCDBG_MAGIC, sizeof(VCDBG_MAGIC));
expected[6] = VCDBG_VERSION >> 8;
expected[7] = VCDBG_VERSION & 0xFF;
connection->recv(buffer, sizeof(VCDBG_MAGIC));
connection->send(expected, sizeof(VCDBG_MAGIC));
if (std::memcmp(expected, buffer, sizeof(VCDBG_MAGIC)) == 0) {
initiated = true;
return false;
} else {
connection->close(true);
return true;
}
}
std::string ClientConnection::read() {
auto connection = dynamic_cast<network::ReadableConnection*>(
network.getConnection(this->connection, true)
);
if (connection == nullptr) {
return "";
}
if (!initiated) {
if (initiate(connection)) {
return "";
}
}
if (messageLength == 0) {
if (connection->available() >= sizeof(int32_t)) {
int32_t length = 0;
connection->recv(reinterpret_cast<char*>(&length), sizeof(int32_t));
if (length <= 0) {
logger.error() << "invalid message length " << length;
} else {
logger.info() << "message length " << length;
messageLength = length;
}
}
} else if (connection->available() >= messageLength) {
std::string string(messageLength, 0);
connection->recv(string.data(), messageLength);
messageLength = 0;
return string;
}
return "";
}
void ClientConnection::send(const dv::value& object) {
auto connection = dynamic_cast<network::ReadableConnection*>(
network.getConnection(this->connection, true)
);
if (connection == nullptr) {
return;
}
auto message = json::stringify(object, false);
int32_t length = message.length();
connection->send(reinterpret_cast<char*>(&length), sizeof(int32_t));
connection->send(message.data(), length);
}
void ClientConnection::sendResponse(const std::string& type) {
send(dv::object({{"type", type}}));
}
bool ClientConnection::alive() const {
return network.getConnection(this->connection, true) != nullptr;
}
static network::Server& create_tcp_server(
DebuggingServer& dbgServer, Engine& engine, int port
) {
auto& network = engine.getNetwork();
u64id_t serverId = network.openTcpServer(
port,
[&network, &dbgServer](u64id_t sid, u64id_t id) {
auto& connection = dynamic_cast<network::ReadableConnection&>(
*network.getConnection(id, true)
);
connection.setPrivate(true);
logger.info() << "connected client " << id << ": "
<< connection.getAddress() << ":"
<< connection.getPort();
dbgServer.setClient(id);
}
);
auto& server = *network.getServer(serverId, true);
server.setPrivate(true);
auto& tcpServer = dynamic_cast<network::TcpServer&>(server);
tcpServer.setMaxClientsConnected(1);
logger.info() << "tcp debugging server open at port " << server.getPort();
return tcpServer;
}
static network::Server& create_server(
DebuggingServer& dbgServer, Engine& engine, const std::string& serverString
) {
logger.info() << "starting debugging server";
size_t sepPos = serverString.find(':');
if (sepPos == std::string::npos) {
throw std::runtime_error("invalid debugging server configuration string");
}
auto transport = serverString.substr(0, sepPos);
if (transport == "tcp") {
int port;
try {
port = std::stoi(serverString.substr(sepPos + 1));
} catch (const std::exception& err) {
throw std::runtime_error("invalid tcp port");
}
return create_tcp_server(dbgServer, engine, port);
} else {
throw std::runtime_error(
"unsupported debugging server transport '" + transport + "'"
);
}
}
DebuggingServer::DebuggingServer(
Engine& engine, const std::string& serverString
)
: engine(engine),
server(create_server(*this, engine, serverString)),
connection(nullptr) {
}
DebuggingServer::~DebuggingServer() {
logger.info() << "stopping debugging server";
server.close();
}
bool DebuggingServer::update() {
if (connection == nullptr) {
return false;
}
std::string message = connection->read();
if (message.empty()) {
if (!connection->alive()) {
bool status = performCommand(disconnectAction, dv::object());
connection.reset();
return status;
}
return false;
}
logger.debug() << "received: " << message;
try {
auto obj = json::parse(message);
if (!obj.has("type")) {
logger.error() << "missing message type";
return false;
}
const auto& type = obj["type"].asString();
if (performCommand(type, obj)) {
connection->sendResponse("resumed");
return true;
}
} catch (const std::runtime_error& err) {
logger.error() << "could not to parse message: " << err.what();
}
return false;
}
bool DebuggingServer::performCommand(
const std::string& type, const dv::value& map
) {
if (!connectionEstablished && type == "connect") {
map.at("disconnect-action").get(disconnectAction);
connectionEstablished = true;
logger.info() << "client connection established";
connection->sendResponse("success");
}
if (!connectionEstablished) {
return false;
}
if (type == "terminate") {
engine.quit();
connection->sendResponse("success");
} else if (type == "detach") {
connection->sendResponse("success");
connection.reset();
engine.detachDebugger();
return false;
} else if (type == "set-breakpoint" || type == "remove-breakpoint") {
if (!map.has("source") || !map.has("line"))
return false;
breakpointEvents.push_back(DebuggingEvent {
type[0] == 's'
? DebuggingEventType::SET_BREAKPOINT
: DebuggingEventType::REMOVE_BREAKPOINT,
BreakpointEventDto {
map["source"].asString(),
static_cast<int>(map["line"].asInteger()),
}
});
} else if (type == "step" || type == "step-into-function") {
breakpointEvents.push_back(DebuggingEvent {
type == "step"
? DebuggingEventType::STEP
: DebuggingEventType::STEP_INTO_FUNCTION,
SignalEventDto {}
});
return true;
} else if (type == "resume") {
breakpointEvents.push_back(DebuggingEvent {
DebuggingEventType::RESUME, SignalEventDto {}});
return true;
} else if (type == "get-value") {
if (!map.has("frame") || !map.has("local") || !map.has("path"))
return false;
int frame = map["frame"].asInteger();
int localIndex = map["local"].asInteger();
ValuePath path;
for (const auto& segment : map["path"]) {
if (segment.isString()) {
path.emplace_back(segment.asString());
} else {
path.emplace_back(static_cast<int>(segment.asInteger()));
}
}
breakpointEvents.push_back(DebuggingEvent {
DebuggingEventType::GET_VALUE, GetValueEventDto {
frame, localIndex, std::move(path)
}
});
return true;
} else {
logger.error() << "unsupported command '" << type << "'";
}
return false;
}
void DebuggingServer::pause(
std::string&& reason, std::string&& message, dv::value&& stackTrace
) {
if (connection == nullptr) {
return;
}
auto response = dv::object({{"type", std::string("paused")}});
if (!reason.empty()) {
response["reason"] = std::move(reason);
}
if (!message.empty()) {
response["message"] = std::move(message);
}
if (stackTrace != nullptr) {
response["stack"] = std::move(stackTrace);
}
connection->send(std::move(response));
engine.startPauseLoop();
}
void DebuggingServer::sendValue(
dv::value&& value, int frame, int local, ValuePath&& path
) {
auto pathValue = dv::list();
for (const auto& segment : path) {
if (auto string = std::get_if<std::string>(&segment)) {
pathValue.add(*string);
} else {
pathValue.add(std::get<int>(segment));
}
}
connection->send(dv::object({
{"type", std::string("value")},
{"frame", frame},
{"local", local},
{"path", std::move(pathValue)},
{"value", std::move(value)},
}));
}
void DebuggingServer::setClient(u64id_t client) {
connection =
std::make_unique<ClientConnection>(engine.getNetwork(), client);
connectionEstablished = false;
}
std::vector<DebuggingEvent> DebuggingServer::pullEvents() {
return std::move(breakpointEvents);
}
void DebuggingServer::setDisconnectAction(const std::string& action) {
disconnectAction = action;
}

View File

@ -0,0 +1,106 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <variant>
#include "typedefs.hpp"
namespace network {
class Server;
class Connection;
class ReadableConnection;
class Network;
}
namespace dv {
class value;
}
class Engine;
namespace devtools {
inline constexpr const char VCDBG_MAGIC[8] = "vc-dbg\0";
inline constexpr int VCDBG_VERSION = 1;
class ClientConnection {
public:
ClientConnection(network::Network& network, u64id_t connection)
: network(network), connection(connection) {
}
~ClientConnection();
std::string read();
void send(const dv::value& message);
void sendResponse(const std::string& type);
bool alive() const;
private:
network::Network& network;
size_t messageLength = 0;
u64id_t connection;
bool initiated = false;
bool initiate(network::ReadableConnection* connection);
};
enum class DebuggingEventType {
SET_BREAKPOINT = 1,
REMOVE_BREAKPOINT,
STEP,
STEP_INTO_FUNCTION,
RESUME,
GET_VALUE,
};
struct BreakpointEventDto {
std::string source;
int line;
};
struct SignalEventDto {
};
using ValuePath = std::vector<std::variant<std::string, int>>;
struct GetValueEventDto {
int frame;
int localIndex;
ValuePath path;
};
struct DebuggingEvent {
DebuggingEventType type;
std::variant<BreakpointEventDto, SignalEventDto, GetValueEventDto> data;
};
class DebuggingServer {
public:
DebuggingServer(Engine& engine, const std::string& serverString);
~DebuggingServer();
bool update();
void pause(
std::string&& reason, std::string&& message, dv::value&& stackTrace
);
void sendValue(dv::value&& value, int frame, int local, ValuePath&& path);
void setClient(u64id_t client);
std::vector<DebuggingEvent> pullEvents();
void setDisconnectAction(const std::string& action);
private:
Engine& engine;
network::Server& server;
std::unique_ptr<ClientConnection> connection;
bool connectionEstablished = false;
std::vector<DebuggingEvent> breakpointEvents;
std::string disconnectAction = "resume";
bool performCommand(
const std::string& type, const dv::value& map
);
};
}

View File

@ -1,7 +1,7 @@
#include "Editor.hpp" #include "Editor.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "coders/syntax_parser.hpp" #include "coders/syntax_parser.hpp"
#include "SyntaxProcessor.hpp" #include "SyntaxProcessor.hpp"

View File

@ -1,8 +1,13 @@
#include "Project.hpp" #include "Project.hpp"
#include "data/dv_util.hpp" #include "data/dv_util.hpp"
#include "debug/Logger.hpp"
#include "io/io.hpp"
#include "io/path.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
static debug::Logger logger("project");
Project::~Project() = default; Project::~Project() = default;
dv::value Project::serialize() const { dv::value Project::serialize() const {
@ -18,3 +23,23 @@ void Project::deserialize(const dv::value& src) {
src.at("title").get(title); src.at("title").get(title);
dv::get(src, "base_packs", basePacks); dv::get(src, "base_packs", basePacks);
} }
void Project::loadProjectClientScript() {
io::path scriptFile = "project:project_client.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project client script";
clientScript = scripting::load_client_project_script(scriptFile);
} else {
logger.warning() << "project client script does not exists";
}
}
void Project::loadProjectStartScript() {
io::path scriptFile = "project:start.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project start script";
setupCoroutine = scripting::start_app_script(scriptFile);
} else {
logger.warning() << "project start script does not exists";
}
}

View File

@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include "interfaces/Process.hpp"
#include "interfaces/Serializable.hpp" #include "interfaces/Serializable.hpp"
namespace scripting { namespace scripting {
@ -15,9 +16,13 @@ struct Project : Serializable {
std::string title; std::string title;
std::vector<std::string> basePacks; std::vector<std::string> basePacks;
std::unique_ptr<scripting::IClientProjectScript> clientScript; std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> setupCoroutine;
~Project(); ~Project();
dv::value serialize() const override; dv::value serialize() const override;
void deserialize(const dv::value& src) override; void deserialize(const dv::value& src) override;
void loadProjectClientScript();
void loadProjectStartScript();
}; };

View File

@ -0,0 +1,15 @@
#pragma once
#include <string>
#include <filesystem>
struct CoreParameters {
bool headless = false;
bool testMode = false;
std::filesystem::path resFolder = "res";
std::filesystem::path userFolder = ".";
std::filesystem::path scriptFile;
std::filesystem::path projectFolder;
std::string debugServerString;
int tps = 20;
};

View File

@ -8,36 +8,36 @@
#include "assets/AssetsLoader.hpp" #include "assets/AssetsLoader.hpp"
#include "audio/audio.hpp" #include "audio/audio.hpp"
#include "coders/GLSLExtension.hpp" #include "coders/GLSLExtension.hpp"
#include "coders/imageio.hpp"
#include "coders/json.hpp"
#include "coders/toml.hpp" #include "coders/toml.hpp"
#include "coders/commons.hpp" #include "coders/commons.hpp"
#include "devtools/Editor.hpp" #include "devtools/Editor.hpp"
#include "devtools/Project.hpp" #include "devtools/Project.hpp"
#include "devtools/DebuggingServer.hpp"
#include "content/ContentControl.hpp" #include "content/ContentControl.hpp"
#include "core_defs.hpp" #include "core_defs.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "io/settings_io.hpp"
#include "frontend/locale.hpp" #include "frontend/locale.hpp"
#include "frontend/menu.hpp" #include "frontend/menu.hpp"
#include "frontend/screens/Screen.hpp" #include "frontend/screens/Screen.hpp"
#include "graphics/render/ModelsGenerator.hpp" #include "graphics/render/ModelsGenerator.hpp"
#include "graphics/core/DrawContext.hpp" #include "graphics/core/DrawContext.hpp"
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Shader.hpp" #include "graphics/core/Shader.hpp"
#include "graphics/ui/GUI.hpp" #include "graphics/ui/GUI.hpp"
#include "objects/rigging.hpp" #include "graphics/ui/elements/Menu.hpp"
#include "logic/EngineController.hpp" #include "logic/EngineController.hpp"
#include "logic/CommandsInterpreter.hpp" #include "logic/CommandsInterpreter.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "logic/scripting/scripting_hud.hpp" #include "logic/scripting/scripting_hud.hpp"
#include "network/Network.hpp" #include "network/Network.hpp"
#include "util/platform.hpp" #include "util/platform.hpp"
#include "window/Camera.hpp"
#include "window/input.hpp" #include "window/input.hpp"
#include "window/Window.hpp" #include "window/Window.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "Mainloop.hpp" #include "Mainloop.hpp"
#include "ServerMainloop.hpp" #include "ServerMainloop.hpp"
#include "WindowControl.hpp"
#include "EnginePaths.hpp"
#include <iostream> #include <iostream>
#include <assert.h> #include <assert.h>
@ -48,29 +48,6 @@
static debug::Logger logger("engine"); static debug::Logger logger("engine");
static std::unique_ptr<ImageData> load_icon() {
try {
auto file = "res:textures/misc/icon.png";
if (io::exists(file)) {
return imageio::read(file);
}
} catch (const std::exception& err) {
logger.error() << "could not load window icon: " << err.what();
}
return nullptr;
}
static std::unique_ptr<scripting::IClientProjectScript> load_client_project_script() {
io::path scriptFile = "project:project_client.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project script";
return scripting::load_client_project_script(scriptFile);
} else {
logger.warning() << "project script does not exists";
}
return nullptr;
}
Engine::Engine() = default; Engine::Engine() = default;
Engine::~Engine() = default; Engine::~Engine() = default;
@ -85,7 +62,7 @@ Engine& Engine::getInstance() {
void Engine::onContentLoad() { void Engine::onContentLoad() {
editor->loadTools(); editor->loadTools();
langs::setup(langs::get_current(), paths.resPaths.collectRoots()); langs::setup(langs::get_current(), paths->resPaths.collectRoots());
if (isHeadless()) { if (isHeadless()) {
return; return;
@ -106,26 +83,9 @@ void Engine::onContentLoad() {
} }
void Engine::initializeClient() { void Engine::initializeClient() {
std::string title = project->title; windowControl = std::make_unique<WindowControl>(*this);
if (title.empty()) { auto [window, input] = windowControl->initialize();
title = "VoxelCore v" +
std::to_string(ENGINE_VERSION_MAJOR) + "." +
std::to_string(ENGINE_VERSION_MINOR);
}
if (ENGINE_DEBUG_BUILD) {
title += " [debug]";
}
auto [window, input] = Window::initialize(&settings.display, title);
if (!window || !input){
throw initialize_error("could not initialize window");
}
window->setFramerate(settings.display.framerate.get());
time.set(window->time());
if (auto icon = load_icon()) {
icon->flipY();
window->setIcon(icon.get());
}
this->window = std::move(window); this->window = std::move(window);
this->input = std::move(input); this->input = std::move(input);
@ -135,10 +95,11 @@ void Engine::initializeClient() {
if (ENGINE_DEBUG_BUILD) { if (ENGINE_DEBUG_BUILD) {
menus::create_version_label(*gui); menus::create_version_label(*gui);
} }
keepAlive(settings.display.fullscreen.observe( keepAlive(settings.display.windowMode.observe(
[this](bool value) { [this](int value) {
if (value != this->window->isFullscreen()) { WindowMode mode = static_cast<WindowMode>(value);
this->window->toggleFullscreen(); if (mode != this->window->getMode()) {
this->window->setMode(mode);
} }
}, },
true true
@ -149,6 +110,14 @@ void Engine::initializeClient() {
}, },
true true
)); ));
keepAlive(this->input->addKeyCallback(Keycode::ESCAPE, [this]() {
auto& menu = *gui->getMenu();
if (menu.hasOpenPage() && menu.back()) {
return true;
}
return false;
}));
} }
void Engine::initialize(CoreParameters coreParameters) { void Engine::initialize(CoreParameters coreParameters) {
@ -157,23 +126,28 @@ void Engine::initialize(CoreParameters coreParameters) {
logger.info() << "engine version: " << ENGINE_VERSION_STRING; logger.info() << "engine version: " << ENGINE_VERSION_STRING;
if (params.headless) { if (params.headless) {
logger.info() << "headless mode is enabled"; logger.info() << "engine runs in headless mode";
} }
if (params.projectFolder.empty()) { if (params.projectFolder.empty()) {
params.projectFolder = params.resFolder; params.projectFolder = params.resFolder;
} }
paths.setResourcesFolder(params.resFolder); paths = std::make_unique<EnginePaths>(params);
paths.setUserFilesFolder(params.userFolder);
paths.setProjectFolder(params.projectFolder);
paths.prepare();
loadProject(); loadProject();
editor = std::make_unique<devtools::Editor>(*this); editor = std::make_unique<devtools::Editor>(*this);
cmd = std::make_unique<cmd::CommandsInterpreter>(); cmd = std::make_unique<cmd::CommandsInterpreter>();
network = network::Network::create(settings.network); network = network::Network::create(settings.network);
if (!params.scriptFile.empty()) { if (!params.debugServerString.empty()) {
paths.setScriptFolder(params.scriptFile.parent_path()); try {
debuggingServer = std::make_unique<devtools::DebuggingServer>(
*this, params.debugServerString
);
} catch (const std::runtime_error& err) {
throw initialize_error(
"debugging server error: " + std::string(err.what())
);
}
} }
loadSettings(); loadSettings();
@ -188,7 +162,7 @@ void Engine::initialize(CoreParameters coreParameters) {
langs::locale_by_envlocale(platform::detect_locale()) langs::locale_by_envlocale(platform::detect_locale())
); );
} }
content = std::make_unique<ContentControl>(*project, paths, *input, [this]() { content = std::make_unique<ContentControl>(*project, *paths, *input, [this]() {
onContentLoad(); onContentLoad();
}); });
scripting::initialize(this); scripting::initialize(this);
@ -197,10 +171,13 @@ void Engine::initialize(CoreParameters coreParameters) {
gui->setPageLoader(scripting::create_page_loader()); gui->setPageLoader(scripting::create_page_loader());
} }
keepAlive(settings.ui.language.observe([this](auto lang) { keepAlive(settings.ui.language.observe([this](auto lang) {
langs::setup(lang, paths.resPaths.collectRoots()); langs::setup(lang, paths->resPaths.collectRoots());
}, true)); }, true));
project->clientScript = load_client_project_script(); project->loadProjectStartScript();
if (!params.headless) {
project->loadProjectClientScript();
}
} }
void Engine::loadSettings() { void Engine::loadSettings() {
@ -230,25 +207,17 @@ void Engine::loadControls() {
void Engine::updateHotkeys() { void Engine::updateHotkeys() {
if (input->jpressed(Keycode::F2)) { if (input->jpressed(Keycode::F2)) {
saveScreenshot(); windowControl->saveScreenshot();
} }
if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) && if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) &&
input->jpressed(Keycode::U)) { input->jpressed(Keycode::U)) {
gui->toggleDebug(); gui->toggleDebug();
} }
if (input->jpressed(Keycode::F11)) { if (input->jpressed(Keycode::F11)) {
settings.display.fullscreen.toggle(); windowControl->toggleFullscreen();
} }
} }
void Engine::saveScreenshot() {
auto image = window->takeScreenshot();
image->flipY();
io::path filename = paths.getNewScreenshotFile("png");
imageio::write(filename.string(), image.get());
logger.info() << "saved screenshot as " << filename.string();
}
void Engine::run() { void Engine::run() {
if (params.headless) { if (params.headless) {
ServerMainloop(*this).run(); ServerMainloop(*this).run();
@ -261,6 +230,20 @@ void Engine::postUpdate() {
network->update(); network->update();
postRunnables.run(); postRunnables.run();
scripting::process_post_runnables(); scripting::process_post_runnables();
if (debuggingServer) {
debuggingServer->update();
}
}
void Engine::detachDebugger() {
debuggingServer.reset();
}
void Engine::applicationTick() {
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
project->setupCoroutine->update();
}
} }
void Engine::updateFrontend() { void Engine::updateFrontend() {
@ -272,14 +255,32 @@ void Engine::updateFrontend() {
gui->postAct(); gui->postAct();
} }
void Engine::nextFrame() { void Engine::nextFrame(bool waitForRefresh) {
window->setFramerate( windowControl->nextFrame(waitForRefresh);
window->isIconified() && settings.display.limitFpsIconified.get() }
? 20
: settings.display.framerate.get() void Engine::startPauseLoop() {
); bool initialCursorLocked = false;
window->swapBuffers(); if (!isHeadless()) {
input->pollEvents(); initialCursorLocked = input->isCursorLocked();
if (initialCursorLocked) {
input->toggleCursor();
}
}
while (!isQuitSignal() && debuggingServer) {
network->update();
if (debuggingServer->update()) {
break;
}
if (isHeadless()) {
platform::sleep(1.0 / params.tps * 1000);
} else {
nextFrame(false);
}
}
if (initialCursorLocked) {
input->toggleCursor();
}
} }
void Engine::renderFrame() { void Engine::renderFrame() {
@ -294,7 +295,11 @@ void Engine::saveSettings() {
io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler)); io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler));
if (!params.headless) { if (!params.headless) {
logger.info() << "saving bindings"; logger.info() << "saving bindings";
io::write_string(EnginePaths::CONTROLS_FILE, input->getBindings().write()); if (input) {
io::write_string(
EnginePaths::CONTROLS_FILE, input->getBindings().write()
);
}
} }
} }
@ -313,6 +318,7 @@ void Engine::close() {
logger.info() << "gui finished"; logger.info() << "gui finished";
} }
audio::close(); audio::close();
debuggingServer.reset();
network.reset(); network.reset();
clearKeepedObjects(); clearKeepedObjects();
project.reset(); project.reset();
@ -340,17 +346,18 @@ void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
void Engine::loadAssets() { void Engine::loadAssets() {
logger.info() << "loading assets"; logger.info() << "loading assets";
Shader::preprocessor->setPaths(&paths.resPaths); Shader::preprocessor->setPaths(&paths->resPaths);
auto content = this->content->get(); auto content = this->content->get();
auto new_assets = std::make_unique<Assets>(); auto new_assets = std::make_unique<Assets>();
AssetsLoader loader(*this, *new_assets, paths.resPaths); AssetsLoader loader(*this, *new_assets, paths->resPaths);
AssetsLoader::addDefaults(loader, content); AssetsLoader::addDefaults(loader, content);
// no need // no need
// correct log messages order is more useful // correct log messages order is more useful
bool threading = false; // look at two upper lines // todo: before setting to true, check if GLSLExtension thread safe
bool threading = false; // look at three upper lines
if (threading) { if (threading) {
auto task = loader.startTask([=](){}); auto task = loader.startTask([=](){});
task->waitForEnd(); task->waitForEnd();
@ -387,6 +394,7 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
} }
if (project->clientScript && this->screen) { if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), true); project->clientScript->onScreenChange(this->screen->getName(), true);
window->setShouldRefresh();
} }
} }
@ -420,11 +428,11 @@ Assets* Engine::getAssets() {
} }
EnginePaths& Engine::getPaths() { EnginePaths& Engine::getPaths() {
return paths; return *paths;
} }
ResPaths& Engine::getResPaths() { ResPaths& Engine::getResPaths() {
return paths.resPaths; return paths->resPaths;
} }
std::shared_ptr<Screen> Engine::getScreen() { std::shared_ptr<Screen> Engine::getScreen() {

View File

@ -1,25 +1,27 @@
#pragma once #pragma once
#include "delegates.hpp" #include "CoreParameters.hpp"
#include "typedefs.hpp"
#include "settings.hpp"
#include "io/engine_paths.hpp"
#include "io/settings_io.hpp"
#include "util/ObjectsKeeper.hpp"
#include "PostRunnables.hpp" #include "PostRunnables.hpp"
#include "Time.hpp" #include "Time.hpp"
#include "delegates.hpp"
#include "settings.hpp"
#include "typedefs.hpp"
#include "util/ObjectsKeeper.hpp"
#include <memory> #include <memory>
#include <string> #include <string>
class Window;
class Assets; class Assets;
class Level;
class Screen;
class ContentControl; class ContentControl;
class EngineController; class EngineController;
class EnginePaths;
class Input; class Input;
class Level;
class ResPaths;
class Screen;
class SettingsHandler;
class Window;
class WindowControl;
struct Project; struct Project;
namespace gui { namespace gui {
@ -36,6 +38,7 @@ namespace network {
namespace devtools { namespace devtools {
class Editor; class Editor;
class DebuggingServer;
} }
class initialize_error : public std::runtime_error { class initialize_error : public std::runtime_error {
@ -43,22 +46,12 @@ public:
initialize_error(const std::string& message) : std::runtime_error(message) {} 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;
std::filesystem::path projectFolder;
};
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>; using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
class Engine : public util::ObjectsKeeper { class Engine : public util::ObjectsKeeper {
CoreParameters params; CoreParameters params;
EngineSettings settings; EngineSettings settings;
EnginePaths paths; std::unique_ptr<EnginePaths> paths;
std::unique_ptr<Project> project; std::unique_ptr<Project> project;
std::unique_ptr<SettingsHandler> settingsHandler; std::unique_ptr<SettingsHandler> settingsHandler;
std::unique_ptr<Assets> assets; std::unique_ptr<Assets> assets;
@ -71,6 +64,8 @@ class Engine : public util::ObjectsKeeper {
std::unique_ptr<Input> input; std::unique_ptr<Input> input;
std::unique_ptr<gui::GUI> gui; std::unique_ptr<gui::GUI> gui;
std::unique_ptr<devtools::Editor> editor; std::unique_ptr<devtools::Editor> editor;
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
std::unique_ptr<WindowControl> windowControl;
PostRunnables postRunnables; PostRunnables postRunnables;
Time time; Time time;
OnWorldOpen levelConsumer; OnWorldOpen levelConsumer;
@ -101,9 +96,11 @@ public:
void postUpdate(); void postUpdate();
void applicationTick();
void updateFrontend(); void updateFrontend();
void renderFrame(); void renderFrame();
void nextFrame(); void nextFrame(bool waitForRefresh);
void startPauseLoop();
/// @brief Set screen (scene). /// @brief Set screen (scene).
/// nullptr may be used to delete previous screen before creating new one, /// nullptr may be used to delete previous screen before creating new one,
@ -138,8 +135,6 @@ public:
postRunnables.postRunnable(callback); postRunnables.postRunnable(callback);
} }
void saveScreenshot();
EngineController* getController(); EngineController* getController();
void setLevelConsumer(OnWorldOpen levelConsumer); void setLevelConsumer(OnWorldOpen levelConsumer);
@ -181,4 +176,10 @@ public:
const Project& getProject() { const Project& getProject() {
return *project; return *project;
} }
devtools::DebuggingServer* getDebuggingServer() {
return debuggingServer.get();
}
void detachDebugger();
}; };

View File

@ -1,49 +1,63 @@
#include "engine_paths.hpp" #include "EnginePaths.hpp"
#include "debug/Logger.hpp"
#include "io/devices/StdfsDevice.hpp"
#include "io/devices/ZipFileDevice.hpp"
#include "maths/util.hpp"
#include "typedefs.hpp"
#include "util/platform.hpp"
#include "util/random.hpp"
#include "util/stringutil.hpp"
#include "world/files/WorldFiles.hpp"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <chrono>
#include <sstream> #include <sstream>
#include <stack> #include <stack>
#include "typedefs.hpp" #include <stdexcept>
#include "util/stringutil.hpp"
#include <utility> #include <utility>
#include "io/devices/StdfsDevice.hpp"
#include "io/devices/ZipFileDevice.hpp"
#include "world/files/WorldFiles.hpp"
#include "debug/Logger.hpp"
#include <chrono>
#include "maths/util.hpp"
template<int n>
static std::string generate_random_base64() {
auto now = std::chrono::high_resolution_clock::now();
auto seed = now.time_since_epoch().count();
util::PseudoRandom random(seed); // fixme: replace with safe random
ubyte bytes[n];
random.rand(bytes, n);
return util::base64_urlsafe_encode(bytes, n);
}
namespace fs = std::filesystem; namespace fs = std::filesystem;
static debug::Logger logger("engine-paths"); static std::random_device random_device;
static inline io::path SCREENSHOTS_FOLDER = "user:screenshots"; static inline io::path SCREENSHOTS_FOLDER = "user:screenshots";
static inline io::path CONTENT_FOLDER = "user:content"; static inline io::path CONTENT_FOLDER = "user:content";
static inline io::path WORLDS_FOLDER = "user:worlds"; static inline io::path WORLDS_FOLDER = "user:worlds";
void EnginePaths::prepare() { static debug::Logger logger("engine-paths");
template<int n>
static std::string generate_random_base64() {
auto randomEngine = util::seeded_random_engine(random_device);
static std::uniform_int_distribution<integer_t> dist(0, 0xFF);
ubyte bytes[n];
for (size_t i = 0; i < n; i++) {
bytes[i] = dist(randomEngine);
}
return util::base64_urlsafe_encode(bytes, n);
}
EnginePaths::EnginePaths(CoreParameters& params)
: resourcesFolder(params.resFolder),
userFilesFolder(params.userFolder),
projectFolder(params.projectFolder) {
if (!params.scriptFile.empty()) {
scriptFolder = params.scriptFile.parent_path();
io::set_device("script", std::make_shared<io::StdfsDevice>(*scriptFolder));
}
io::set_device("res", std::make_shared<io::StdfsDevice>(resourcesFolder, false)); io::set_device("res", std::make_shared<io::StdfsDevice>(resourcesFolder, false));
io::set_device("user", std::make_shared<io::StdfsDevice>(userFilesFolder)); io::set_device("user", std::make_shared<io::StdfsDevice>(userFilesFolder));
io::set_device("project", std::make_shared<io::StdfsDevice>(projectFolder));
if (!io::is_directory("res:")) { if (!io::is_directory("res:")) {
throw std::runtime_error( throw std::runtime_error(
resourcesFolder.string() + " is not a directory" resourcesFolder.string() + " is not a directory"
); );
} }
logger.info() << "executable path: " << platform::get_executable_path().string();
logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string(); logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string();
logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string(); logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string();
logger.info() << "project folder: " << fs::canonical(projectFolder).u8string(); logger.info() << "project folder: " << fs::canonical(projectFolder).u8string();
@ -57,15 +71,7 @@ void EnginePaths::prepare() {
io::create_subdevice("config", "user", "config"); io::create_subdevice("config", "user", "config");
} }
const std::filesystem::path& EnginePaths::getUserFilesFolder() const { io::path EnginePaths::getNewScreenshotFile(const std::string& ext) const {
return userFilesFolder;
}
const std::filesystem::path& EnginePaths::getResourcesFolder() const {
return resourcesFolder;
}
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) {
auto folder = SCREENSHOTS_FOLDER; auto folder = SCREENSHOTS_FOLDER;
if (!io::is_directory(folder)) { if (!io::is_directory(folder)) {
io::create_directories(folder); io::create_directories(folder);
@ -93,10 +99,6 @@ io::path EnginePaths::getWorldsFolder() const {
return WORLDS_FOLDER; return WORLDS_FOLDER;
} }
io::path EnginePaths::getCurrentWorldFolder() {
return currentWorldFolder;
}
io::path EnginePaths::getWorldFolderByName(const std::string& name) { io::path EnginePaths::getWorldFolderByName(const std::string& name) {
return getWorldsFolder() / name; return getWorldsFolder() / name;
} }
@ -130,24 +132,6 @@ std::vector<io::path> EnginePaths::scanForWorlds() const {
return folders; return folders;
} }
void EnginePaths::setUserFilesFolder(std::filesystem::path folder) {
this->userFilesFolder = std::move(folder);
}
void EnginePaths::setResourcesFolder(std::filesystem::path folder) {
this->resourcesFolder = std::move(folder);
}
void EnginePaths::setScriptFolder(std::filesystem::path folder) {
io::set_device("script", std::make_shared<io::StdfsDevice>(folder));
this->scriptFolder = std::move(folder);
}
void EnginePaths::setProjectFolder(std::filesystem::path folder) {
io::set_device("project", std::make_shared<io::StdfsDevice>(folder));
this->projectFolder = std::move(folder);
}
void EnginePaths::setCurrentWorldFolder(io::path folder) { void EnginePaths::setCurrentWorldFolder(io::path folder) {
if (folder.empty()) { if (folder.empty()) {
io::remove_device("world"); io::remove_device("world");

View File

@ -1,15 +1,15 @@
#pragma once #pragma once
#include "io/io.hpp"
#include "data/dv.hpp"
#include "CoreParameters.hpp"
#include <unordered_map> #include <unordered_map>
#include <stdexcept>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <tuple> #include <tuple>
#include "io.hpp"
#include "data/dv.hpp"
struct PathsRoot { struct PathsRoot {
std::string name; std::string name;
io::path path; io::path path;
@ -46,24 +46,13 @@ class EnginePaths {
public: public:
ResPaths resPaths; ResPaths resPaths;
void prepare(); EnginePaths(CoreParameters& params);
void setUserFilesFolder(std::filesystem::path folder);
const std::filesystem::path& getUserFilesFolder() const;
void setResourcesFolder(std::filesystem::path folder);
const std::filesystem::path& getResourcesFolder() const;
void setScriptFolder(std::filesystem::path folder);
void setProjectFolder(std::filesystem::path folder);
io::path getWorldFolderByName(const std::string& name); io::path getWorldFolderByName(const std::string& name);
io::path getWorldsFolder() const; io::path getWorldsFolder() const;
void setCurrentWorldFolder(io::path folder); void setCurrentWorldFolder(io::path folder);
io::path getCurrentWorldFolder(); io::path getNewScreenshotFile(const std::string& ext) const;
io::path getNewScreenshotFile(const std::string& ext);
std::string mount(const io::path& file); std::string mount(const io::path& file);
void unmount(const std::string& name); void unmount(const std::string& name);
@ -80,9 +69,9 @@ public:
static inline io::path CONTROLS_FILE = "user:controls.toml"; static inline io::path CONTROLS_FILE = "user:controls.toml";
static inline io::path SETTINGS_FILE = "user:settings.toml"; static inline io::path SETTINGS_FILE = "user:settings.toml";
private: private:
std::filesystem::path userFilesFolder {"."}; std::filesystem::path resourcesFolder;
std::filesystem::path resourcesFolder {"res"}; std::filesystem::path userFilesFolder;
std::filesystem::path projectFolder = resourcesFolder; std::filesystem::path projectFolder;
io::path currentWorldFolder; io::path currentWorldFolder;
std::optional<std::filesystem::path> scriptFolder; std::optional<std::filesystem::path> scriptFolder;
std::vector<PathsRoot> entryPoints; std::vector<PathsRoot> entryPoints;

View File

@ -18,6 +18,7 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
void Mainloop::run() { void Mainloop::run() {
auto& time = engine.getTime(); auto& time = engine.getTime();
auto& window = engine.getWindow(); auto& window = engine.getWindow();
auto& settings = engine.getSettings();
engine.setLevelConsumer([this](auto level, int64_t localPlayer) { engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
if (level == nullptr) { if (level == nullptr) {
@ -38,13 +39,17 @@ void Mainloop::run() {
logger.info() << "main loop started"; logger.info() << "main loop started";
while (!window.isShouldClose()){ while (!window.isShouldClose()){
time.update(window.time()); time.update(window.time());
engine.applicationTick();
engine.updateFrontend(); engine.updateFrontend();
if (!window.isIconified()) { if (!window.isIconified()) {
engine.renderFrame(); engine.renderFrame();
} }
engine.postUpdate(); engine.postUpdate();
engine.nextFrame(); engine.nextFrame(
settings.display.adaptiveFpsInMenu.get() &&
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
);
} }
logger.info() << "main loop stopped"; logger.info() << "main loop stopped";
} }

View File

@ -1,6 +1,7 @@
#include "ServerMainloop.hpp" #include "ServerMainloop.hpp"
#include "Engine.hpp" #include "Engine.hpp"
#include "EnginePaths.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "logic/LevelController.hpp" #include "logic/LevelController.hpp"
#include "interfaces/Process.hpp" #include "interfaces/Process.hpp"
@ -15,8 +16,6 @@ using namespace std::chrono;
static debug::Logger logger("mainloop"); static debug::Logger logger("mainloop");
inline constexpr int TPS = 20;
ServerMainloop::ServerMainloop(Engine& engine) : engine(engine) { ServerMainloop::ServerMainloop(Engine& engine) : engine(engine) {
} }
@ -34,12 +33,11 @@ void ServerMainloop::run() {
setLevel(std::move(level)); setLevel(std::move(level));
}); });
logger.info() << "starting test " << coreParams.scriptFile.string(); auto process = scripting::start_app_script(
auto process = scripting::start_coroutine(
"script:" + coreParams.scriptFile.filename().u8string() "script:" + coreParams.scriptFile.filename().u8string()
); );
double targetDelta = 1.0 / static_cast<double>(TPS); double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
double delta = targetDelta; double delta = targetDelta;
auto begin = system_clock::now(); auto begin = system_clock::now();
auto startupTime = begin; auto startupTime = begin;
@ -63,6 +61,7 @@ void ServerMainloop::run() {
controller->getLevel()->getWorld()->updateTimers(delta); controller->getLevel()->getWorld()->updateTimers(delta);
controller->update(glm::min(delta, 0.2), false); controller->update(glm::min(delta, 0.2), false);
} }
engine.applicationTick();
engine.postUpdate(); engine.postUpdate();
if (!coreParams.testMode) { if (!coreParams.testMode) {

View File

@ -0,0 +1,93 @@
#include "WindowControl.hpp"
#include "Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "devtools/Project.hpp"
#include "coders/imageio.hpp"
#include "window/Window.hpp"
#include "window/input.hpp"
#include "debug/Logger.hpp"
#include "graphics/core/ImageData.hpp"
#include "util/platform.hpp"
static debug::Logger logger("window-control");
namespace {
static std::unique_ptr<ImageData> load_icon() {
try {
auto file = "res:textures/misc/icon.png";
if (io::exists(file)) {
return imageio::read(file);
}
} catch (const std::exception& err) {
logger.error() << "could not load window icon: " << err.what();
}
return nullptr;
}
}
WindowControl::WindowControl(Engine& engine) : engine(engine) {}
WindowControl::Result WindowControl::initialize() {
const auto& project = engine.getProject();
auto& settings = engine.getSettings();
std::string title = project.title;
if (title.empty()) {
title = "VoxelCore v" +
std::to_string(ENGINE_VERSION_MAJOR) + "." +
std::to_string(ENGINE_VERSION_MINOR);
}
if (ENGINE_DEBUG_BUILD) {
title += " [debug]";
}
if (engine.getDebuggingServer()) {
title = "[debugging] " + title;
}
auto [window, input] = Window::initialize(&settings.display, title);
if (!window || !input){
throw initialize_error("could not initialize window");
}
window->setFramerate(settings.display.framerate.get());
if (auto icon = load_icon()) {
icon->flipY();
window->setIcon(icon.get());
}
return Result {std::move(window), std::move(input)};
}
void WindowControl::saveScreenshot() {
auto& window = engine.getWindow();
const auto& paths = engine.getPaths();
auto image = window.takeScreenshot();
image->flipY();
io::path filename = paths.getNewScreenshotFile("png");
imageio::write(filename.string(), image.get());
logger.info() << "saved screenshot as " << filename.string();
}
void WindowControl::toggleFullscreen() {
auto& settings = engine.getSettings();
auto& windowMode = settings.display.windowMode;
if (windowMode.get() != static_cast<int>(WindowMode::FULLSCREEN)) {
windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
} else {
windowMode.set(static_cast<int>(WindowMode::WINDOWED));
}
}
void WindowControl::nextFrame(bool waitForRefresh) {
const auto& settings = engine.getSettings();
auto& window = engine.getWindow();
auto& input = engine.getInput();
window.setFramerate(
window.isIconified() && settings.display.limitFpsIconified.get()
? 20
: settings.display.framerate.get()
);
window.swapBuffers();
input.pollEvents(waitForRefresh && !window.checkShouldRefresh());
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <memory>
class Window;
class Input;
class Engine;
class WindowControl {
public:
struct Result {
std::unique_ptr<Window> window;
std::unique_ptr<Input> input;
};
WindowControl(Engine& engine);
Result initialize();
void nextFrame(bool waitForRefresh);
void saveScreenshot();
void toggleFullscreen();
private:
Engine& engine;
};

View File

@ -49,8 +49,7 @@ LevelFrontend::LevelFrontend(
auto sound = rassets.get<audio::Sound>(material->stepsSound); auto sound = rassets.get<audio::Sound>(material->stepsSound);
glm::vec3 pos {}; glm::vec3 pos {};
auto soundsCamera = currentPlayer->currentCamera.get(); auto soundsCamera = currentPlayer->currentCamera.get();
if (soundsCamera == currentPlayer->spCamera.get() || if (currentPlayer->isCurrentCameraBuiltin()) {
soundsCamera == currentPlayer->tpCamera.get()) {
soundsCamera = currentPlayer->fpCamera.get(); soundsCamera = currentPlayer->fpCamera.get();
} }
bool relative = player == currentPlayer && bool relative = player == currentPlayer &&

View File

@ -9,6 +9,7 @@
#include "graphics/ui/elements/TextBox.hpp" #include "graphics/ui/elements/TextBox.hpp"
#include "graphics/ui/elements/TrackBar.hpp" #include "graphics/ui/elements/TrackBar.hpp"
#include "graphics/ui/elements/InputBindBox.hpp" #include "graphics/ui/elements/InputBindBox.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/render/WorldRenderer.hpp" #include "graphics/render/WorldRenderer.hpp"
#include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp"
#include "graphics/render/ChunksRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp"
@ -43,10 +44,15 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
return label; return label;
} }
static bool should_keep_previous(GUI& gui) {
return !gui.getInput().isCursorLocked();
}
// TODO: move to xml // TODO: move to xml
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally
std::shared_ptr<UINode> create_debug_panel( std::shared_ptr<UINode> create_debug_panel(
Engine& engine, Engine& engine,
Level& level, Level& level,
@ -81,7 +87,7 @@ std::shared_ptr<UINode> create_debug_panel(
fpsMax = fps; fpsMax = fps;
}); });
panel->listenInterval(1.0f, [&engine]() { panel->listenInterval(1.0f, [&engine, &gui]() {
const auto& network = engine.getNetwork(); const auto& network = engine.getNetwork();
size_t totalDownload = network.getTotalDownload(); size_t totalDownload = network.getTotalDownload();
size_t totalUpload = network.getTotalUpload(); size_t totalUpload = network.getTotalUpload();
@ -135,7 +141,16 @@ std::shared_ptr<UINode> create_debug_panel(
std::to_wstring(player.getId()); std::to_wstring(player.getId());
})); }));
panel->add(create_label(gui, [&]() -> std::wstring { panel->add(create_label(gui, [&]() -> std::wstring {
const auto& vox = player.selection.vox; // TODO: move to xml finally
static voxel prevVox = {BLOCK_VOID, {}};
auto vox = player.selection.vox;
if (vox.id == BLOCK_VOID && should_keep_previous(gui)) {
vox = prevVox;
} else {
prevVox = vox;
}
std::wstringstream stream; std::wstringstream stream;
stream << "r:" << vox.state.rotation << " s:" stream << "r:" << vox.state.rotation << " s:"
<< std::bitset<3>(vox.state.segment) << " u:" << std::bitset<3>(vox.state.segment) << " u:"
@ -148,8 +163,17 @@ std::shared_ptr<UINode> create_debug_panel(
} }
})); }));
panel->add(create_label(gui, [&]() -> std::wstring { panel->add(create_label(gui, [&]() -> std::wstring {
const auto& selection = player.selection; // TODO: move to xml finally
static CursorSelection prevSelection {};
auto selection = player.selection;
const auto& vox = selection.vox; const auto& vox = selection.vox;
if (vox.id == BLOCK_VOID && should_keep_previous(gui)) {
selection = prevSelection;
} else {
prevSelection = selection;
}
if (vox.id == BLOCK_VOID) { if (vox.id == BLOCK_VOID) {
return L"x: - y: - z: -"; return L"x: - y: - z: -";
} }
@ -158,7 +182,16 @@ std::shared_ptr<UINode> create_debug_panel(
L" z: " + std::to_wstring(selection.actualPosition.z); L" z: " + std::to_wstring(selection.actualPosition.z);
})); }));
panel->add(create_label(gui, [&]() { panel->add(create_label(gui, [&]() {
// TODO: move to xml finally
static entityid_t prevEid = ENTITY_NONE;
auto eid = player.getSelectedEntity(); auto eid = player.getSelectedEntity();
if (eid == ENTITY_NONE && should_keep_previous(gui)) {
eid = prevEid;
} else {
prevEid = eid;
}
if (eid == ENTITY_NONE) { if (eid == ENTITY_NONE) {
return std::wstring {L"entity: -"}; return std::wstring {L"entity: -"};
} else if (auto entity = level.entities->get(eid)) { } else if (auto entity = level.entities->get(eid)) {

View File

@ -469,6 +469,7 @@ void Hud::showExchangeSlot() {
gui, gui,
SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr) SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr)
); );
exchangeSlot->setId("hud.exchange-slot");
exchangeSlot->bind(exchangeSlotInv->getId(), exchangeSlotInv->getSlot(0), &content); exchangeSlot->bind(exchangeSlotInv->getId(), exchangeSlotInv->getSlot(0), &content);
exchangeSlot->setColor(glm::vec4()); exchangeSlot->setColor(glm::vec4());
exchangeSlot->setInteractive(false); exchangeSlot->setInteractive(false);
@ -756,3 +757,13 @@ void Hud::setAllowPause(bool flag) {
} }
allowPause = flag; allowPause = flag;
} }
bool Hud::isOpen(const std::string& layoutid) const {
for (const auto& element : elements) {
auto doc = element.getDocument();
if (doc && doc->getId() == layoutid) {
return true;
}
}
return false;
}

View File

@ -213,6 +213,8 @@ public:
void setAllowPause(bool flag); void setAllowPause(bool flag);
bool isOpen(const std::string& layoutid) const;
static bool showGeneratorMinimap; static bool showGeneratorMinimap;
/// @brief Runtime updating debug visualization texture /// @brief Runtime updating debug visualization texture

View File

@ -11,7 +11,7 @@
#include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/elements/Menu.hpp"
#include "graphics/ui/gui_util.hpp" #include "graphics/ui/gui_util.hpp"
#include "interfaces/Task.hpp" #include "interfaces/Task.hpp"
#include "io/engine_paths.hpp" #include "engine/EnginePaths.hpp"
#include "locale.hpp" #include "locale.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "screens/MenuScreen.hpp" #include "screens/MenuScreen.hpp"

View File

@ -6,6 +6,7 @@
#include "core_defs.hpp" #include "core_defs.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "frontend/ContentGfxCache.hpp" #include "frontend/ContentGfxCache.hpp"
#include "frontend/LevelFrontend.hpp" #include "frontend/LevelFrontend.hpp"
@ -75,13 +76,14 @@ LevelScreen::LevelScreen(
engine, *controller, *renderer, assets, *player engine, *controller, *renderer, assets, *player
); );
keepAlive(settings.graphics.backlight.observe([=](bool) { auto resetChunks = [=](bool) {
player->chunks->saveAndClear();
renderer->clear();
}));
keepAlive(settings.graphics.denseRender.observe([=](bool) {
player->chunks->saveAndClear(); player->chunks->saveAndClear();
renderer->clear(); renderer->clear();
};
keepAlive(settings.graphics.backlight.observe(resetChunks));
keepAlive(settings.graphics.softLighting.observe(resetChunks));
keepAlive(settings.graphics.denseRender.observe([=](bool flag) {
resetChunks(flag);
frontend->getContentGfxCache().refresh(); frontend->getContentGfxCache().refresh();
})); }));
keepAlive(settings.camera.fov.observe([=](double value) { keepAlive(settings.camera.fov.observe([=](double value) {

View File

@ -12,9 +12,11 @@
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
MenuScreen::MenuScreen(Engine& engine) : Screen(engine) { MenuScreen::MenuScreen(Engine& engine)
uicamera = : Screen(engine),
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y); uicamera(
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y)
) {
uicamera->perspective = false; uicamera->perspective = false;
uicamera->near = -1.0f; uicamera->near = -1.0f;
uicamera->far = 1.0f; uicamera->far = 1.0f;
@ -24,7 +26,7 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
MenuScreen::~MenuScreen() = default; MenuScreen::~MenuScreen() = default;
void MenuScreen::onOpen() { void MenuScreen::onOpen() {
engine.getContentControl().resetContent(); engine.getContentControl().resetContent({});
auto menu = engine.getGUI().getMenu(); auto menu = engine.getGUI().getMenu();
menu->reset(); menu->reset();

View File

@ -8,21 +8,6 @@ inline constexpr glm::vec3 X(1, 0, 0);
inline constexpr glm::vec3 Y(0, 1, 0); inline constexpr glm::vec3 Y(0, 1, 0);
inline constexpr glm::vec3 Z(0, 0, 1); inline constexpr glm::vec3 Z(0, 0, 1);
void Mesh::addPlane(
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm
) {
vertices.push_back({pos-right-up, {0,0}, norm});
vertices.push_back({pos+right-up, {1,0}, norm});
vertices.push_back({pos+right+up, {1,1}, norm});
vertices.push_back({pos-right-up, {0,0}, norm});
vertices.push_back({pos+right+up, {1,1}, norm});
vertices.push_back({pos-right+up, {0,1}, norm});
}
void Mesh::addPlane( void Mesh::addPlane(
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right, const glm::vec3& right,
@ -39,6 +24,23 @@ void Mesh::addPlane(
vertices.push_back({pos-right+up, {uv.u1, uv.v2}, norm}); vertices.push_back({pos-right+up, {uv.u1, uv.v2}, norm});
} }
void Mesh::addPlane(
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm,
const UVRegion& region,
const glm::mat4& transform
) {
addPlane(
glm::vec3(transform * glm::vec4(pos, 1.0f)),
glm::vec3(transform * glm::vec4(right, 0.0f)),
glm::vec3(transform * glm::vec4(up, 0.0f)),
glm::normalize(glm::vec3(transform * glm::vec4(norm, 0.0f))),
region
);
}
void Mesh::addRect( void Mesh::addRect(
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right, const glm::vec3& right,
@ -56,14 +58,15 @@ void Mesh::addRect(
} }
void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) { void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) {
addPlane(pos+Z*size, X*size, Y*size, Z); UVRegion fullRegion (0, 0, 1, 1);
addPlane(pos-Z*size, -X*size, Y*size, -Z); addPlane(pos+Z*size, X*size, Y*size, Z, fullRegion);
addPlane(pos-Z*size, -X*size, Y*size, -Z, fullRegion);
addPlane(pos+Y*size, X*size, -Z*size, Y); addPlane(pos+Y*size, X*size, -Z*size, Y, fullRegion);
addPlane(pos-Y*size, X*size, Z*size, -Y); addPlane(pos-Y*size, X*size, Z*size, -Y, fullRegion);
addPlane(pos+X*size, -Z*size, Y*size, X); addPlane(pos+X*size, -Z*size, Y*size, X, fullRegion);
addPlane(pos-X*size, Z*size, Y*size, -X); addPlane(pos-X*size, Z*size, Y*size, -X, fullRegion);
} }
void Mesh::addBox( void Mesh::addBox(
@ -71,19 +74,29 @@ void Mesh::addBox(
const glm::vec3& size, const glm::vec3& size,
const UVRegion (&uvs)[6], const UVRegion (&uvs)[6],
const bool enabledSides[6] const bool enabledSides[6]
) {
addBox(pos, size, uvs, enabledSides, glm::mat4(1.0f));
}
void Mesh::addBox(
const glm::vec3& pos,
const glm::vec3& size,
const UVRegion (&uvs)[6],
const bool enabledSides[6],
const glm::mat4& transform
) { ) {
if (enabledSides[0]) // north if (enabledSides[0]) // north
addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0]); addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0], transform);
if (enabledSides[1]) // south if (enabledSides[1]) // south
addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1]); addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1], transform);
if (enabledSides[2]) // top if (enabledSides[2]) // top
addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2] * glm::vec2(-1)); addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2] * glm::vec2(-1), transform);
if (enabledSides[3]) // bottom if (enabledSides[3]) // bottom
addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3] * glm::vec2(-1, 1)); addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3] * glm::vec2(-1, 1), transform);
if (enabledSides[4]) // west if (enabledSides[4]) // west
addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4]); addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4], transform);
if (enabledSides[5]) // east if (enabledSides[5]) // east
addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5] * glm::vec2(-1, 1)); addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5] * glm::vec2(-1, 1), transform);
} }
void Mesh::scale(const glm::vec3& size) { void Mesh::scale(const glm::vec3& size) {

View File

@ -22,14 +22,16 @@ namespace model {
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right, const glm::vec3& right,
const glm::vec3& up, const glm::vec3& up,
const glm::vec3& norm const glm::vec3& norm,
const UVRegion& region
); );
void addPlane( void addPlane(
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right, const glm::vec3& right,
const glm::vec3& up, const glm::vec3& up,
const glm::vec3& norm, const glm::vec3& norm,
const UVRegion& region const UVRegion& region,
const glm::mat4& transform
); );
void addRect( void addRect(
const glm::vec3& pos, const glm::vec3& pos,
@ -45,6 +47,13 @@ namespace model {
const UVRegion (&texfaces)[6], const UVRegion (&texfaces)[6],
const bool enabledSides[6] const bool enabledSides[6]
); );
void addBox(
const glm::vec3& pos,
const glm::vec3& size,
const UVRegion (&texfaces)[6],
const bool enabledSides[6],
const glm::mat4& transform
);
void scale(const glm::vec3& size); void scale(const glm::vec3& size);
}; };

View File

@ -49,6 +49,14 @@ ImageData* Atlas::getImage() const {
return image.get(); return image.get();
} }
std::shared_ptr<Texture> Atlas::shareTexture() const {
return texture;
}
std::shared_ptr<ImageData> Atlas::shareImageData() const {
return image;
}
void AtlasBuilder::add(const std::string& name, std::unique_ptr<ImageData> image) { void AtlasBuilder::add(const std::string& name, std::unique_ptr<ImageData> image) {
entries.push_back(atlasentry{name, std::shared_ptr<ImageData>(image.release())}); entries.push_back(atlasentry{name, std::shared_ptr<ImageData>(image.release())});
names.insert(name); names.insert(name);

View File

@ -14,8 +14,8 @@ class ImageData;
class Texture; class Texture;
class Atlas { class Atlas {
std::unique_ptr<Texture> texture; std::shared_ptr<Texture> texture;
std::unique_ptr<ImageData> image; std::shared_ptr<ImageData> image;
std::unordered_map<std::string, UVRegion> regions; std::unordered_map<std::string, UVRegion> regions;
public: public:
/// @param image atlas raster /// @param image atlas raster
@ -36,6 +36,9 @@ public:
Texture* getTexture() const; Texture* getTexture() const;
ImageData* getImage() const; ImageData* getImage() const;
std::shared_ptr<Texture> shareTexture() const;
std::shared_ptr<ImageData> shareImageData() const;
}; };
struct atlasentry { struct atlasentry {

View File

@ -25,7 +25,7 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
0, 0,
format, format,
GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE,
NULL nullptr
); );
} }
} }

View File

@ -95,6 +95,17 @@ void ImageData::blit(const ImageData& image, int x, int y) {
throw std::runtime_error("mismatching format"); throw std::runtime_error("mismatching format");
} }
std::unique_ptr<ImageData> ImageData::cropped(int x, int y, int width, int height) const {
width = std::min<int>(width, this->width - x);
height = std::min<int>(height, this->height - y);
if (width <= 0 || height <= 0) {
throw std::runtime_error("invalid crop dimensions");
}
auto subImage = std::make_unique<ImageData>(format, width, height);
subImage->blitMatchingFormat(*this, -x, -y);
return subImage;
}
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) {
const int left = 0; const int left = 0;
const int right = width; const int right = width;
@ -409,6 +420,99 @@ void ImageData::fixAlphaColor() {
} }
} }
static void check_matching(const ImageData& a, const ImageData& b) {
if (b.getWidth() != a.getWidth() ||
b.getHeight() != a.getHeight() ||
b.getFormat() != a.getFormat()) {
throw std::runtime_error("image sizes or formats do not match");
}
}
void ImageData::mulColor(const glm::ivec4& color) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width; x++) {
uint idx = (y * width + x) * comps;
for (uint c = 0; c < comps; c++) {
float val = static_cast<float>(data[idx + c]) * color[c] / 255.0f;
data[idx + c] =
static_cast<ubyte>(std::min(std::max(val, 0.0f), 255.0f));
}
}
}
}
void ImageData::addColor(const ImageData& other, int multiplier) {
check_matching(*this, other);
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width; x++) {
uint idx = (y * width + x) * comps;
for (uint c = 0; c < comps; c++) {
int val = data[idx + c] + other.data[idx + c] * multiplier;
data[idx + c] =
static_cast<ubyte>(std::min(std::max(val, 0), 255));
}
}
}
}
void ImageData::addColor(const glm::ivec4& color, int multiplier) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width; x++) {
uint idx = (y * width + x) * comps;
for (uint c = 0; c < comps; c++) {
int val = data[idx + c] + color[c] * multiplier;
data[idx + c] =
static_cast<ubyte>(std::min(std::max(val, 0), 255));
}
}
}
}
void ImageData::mulColor(const ImageData& other) {
check_matching(*this, other);
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width; x++) {
uint idx = (y * width + x) * comps;
for (uint c = 0; c < comps; c++) {
float val = static_cast<float>(data[idx + c]) *
static_cast<float>(other.data[idx + c]) / 255.0f;
data[idx + c] =
static_cast<ubyte>(std::min(std::max(val, 0.0f), 255.0f));
}
}
}
}
std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size) { std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size) {
// RGBA is only supported // RGBA is only supported
assert(image->getFormat() == ImageFormat::rgba8888); assert(image->getFormat() == ImageFormat::rgba8888);

Some files were not shown because too many files have changed in this diff Show More