commit
3fb42efc03
2
.github/workflows/appimage.yml
vendored
2
.github/workflows/appimage.yml
vendored
@ -2,7 +2,7 @@ name: x86-64 AppImage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "dev", "release-**"]
|
||||
branches: [ "main", "release-**"]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
|
||||
|
||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@ -2,7 +2,7 @@ name: Macos Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "dev", "release-**"]
|
||||
branches: [ "main", "release-**"]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
|
||||
|
||||
2
.github/workflows/windows-clang.yml
vendored
2
.github/workflows/windows-clang.yml
vendored
@ -2,7 +2,7 @@ name: Windows Build (CLang)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "dev", "release-**"]
|
||||
branches: [ "main", "release-**"]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
|
||||
|
||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@ -2,7 +2,7 @@ name: MSVC Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "dev", "release-**"]
|
||||
branches: [ "main", "release-**"]
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
## Latest release
|
||||
|
||||
- [Download](https://github.com/MihailRis/VoxelCore/releases/latest) | [Скачать](https://github.com/MihailRis/VoxelCore/releases/latest)
|
||||
- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.28/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.28/doc/ru/main-page.md)
|
||||
- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.29/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.29/doc/ru/main-page.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -203,3 +203,79 @@ audio.count_speakers() -> integer
|
||||
-- get current number of playing streams
|
||||
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)
|
||||
```
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
# 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
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ The script/test name without the path and extension is available as `app.script`
|
||||
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
|
||||
|
||||
```lua
|
||||
|
||||
@ -25,4 +25,12 @@ assets.parse_model(
|
||||
-- Model name after loading
|
||||
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
|
||||
```
|
||||
|
||||
@ -20,6 +20,12 @@ hud.open(
|
||||
[optional] invid: int
|
||||
) -> int
|
||||
|
||||
-- Returns true if specified layout is open.
|
||||
hud.is_open(
|
||||
layoutid: str
|
||||
) -> bool
|
||||
|
||||
|
||||
-- Open block UI and inventory.
|
||||
-- Throws an exception if block has no UI layout.
|
||||
-- Returns block inventory ID (if *"inventory-size"=0* a virtual
|
||||
|
||||
@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int}
|
||||
|
||||
Returns cursor screen position.
|
||||
|
||||
```lua
|
||||
input.get_mouse_delta() --> {int, int}
|
||||
```
|
||||
|
||||
Returns cursor movement delta.
|
||||
|
||||
```lua
|
||||
input.get_bindings() --> strings array
|
||||
```
|
||||
|
||||
@ -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.
|
||||
|
||||
```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
|
||||
player.set_spawnpoint(playerid: int, x: number, y: number, z: 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
|
||||
|
||||
```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.
|
||||
|
||||
@ -37,6 +37,7 @@ Properties that apply to all elements:
|
||||
| Name | Type | Read | Write | Description |
|
||||
| ------------- | ------- | ---- | ----- | ------------------------------------------- |
|
||||
| id | string | yes | *no* | element id |
|
||||
| exists | bool | yes | *no* | checks if element exists |
|
||||
| pos | vec2 | yes | yes | element position inside a container |
|
||||
| wpos | vec2 | yes | yes | element position inside the window |
|
||||
| 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:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) |
|
||||
| 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)
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.
|
||||
|
||||
Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
|
||||
|
||||
### Звук (Sound)
|
||||
|
||||
Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.
|
||||
@ -203,3 +204,79 @@ audio.count_speakers() -> 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)
|
||||
```
|
||||
|
||||
@ -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)
|
||||
|
||||
## Разделы
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
local filename = "script:"..app.script..".lua"
|
||||
```
|
||||
|
||||
Так как управляющий сценарий может не принадлежать ни одному из паков, он не относиться к своему паку и имеет собственное пространство имён, в котором доступны все глобальные функции и таблицы, а также библиотека `app`.
|
||||
|
||||
## Функции
|
||||
|
||||
```lua
|
||||
|
||||
@ -25,4 +25,12 @@ assets.parse_model(
|
||||
-- Имя модели после загрузки
|
||||
name: str
|
||||
)
|
||||
|
||||
-- Создаёт холст (Canvas) из загруженной текстуры
|
||||
assets.to_canvas(
|
||||
-- Имя загруженной текстуры.
|
||||
-- Поддерживается как отдельные ("имя_текстуры"),
|
||||
-- так и находящиеся в атласе ("атлас:имя_текстуры").
|
||||
name: str
|
||||
) --> Canvas
|
||||
```
|
||||
|
||||
25
doc/ru/scripting/builtins/libcompression.md
Normal file
25
doc/ru/scripting/builtins/libcompression.md
Normal 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
|
||||
```
|
||||
@ -205,4 +205,4 @@ file.open_named_pipe(имя: str, режим: str) -> io_stream
|
||||
|
||||
`/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически.
|
||||
|
||||
Доступные режимы такие же, как и в `file.open`, за исключением `+`
|
||||
Доступные режимы такие же, как и в `file.open`, за исключением `+`
|
||||
|
||||
@ -20,6 +20,10 @@ hud.open(
|
||||
[опционально] invid: int
|
||||
) -> int
|
||||
|
||||
-- Возвращает true если указаный макет UI открыт.
|
||||
hud.is_open(
|
||||
layoutid: str
|
||||
) -> bool
|
||||
|
||||
-- Открывает инвентарь и UI блока.
|
||||
-- Если блок не имеет макета UI - бросается исключение.
|
||||
|
||||
@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int}
|
||||
|
||||
Возвращает позицию курсора на экране.
|
||||
|
||||
```lua
|
||||
input.get_mouse_delta() --> {int, int}
|
||||
```
|
||||
|
||||
Возращает дельту позиции курсора.
|
||||
|
||||
```lua
|
||||
input.get_bindings() --> массив строк
|
||||
```
|
||||
|
||||
@ -93,6 +93,12 @@ socket:is_connected() --> bool
|
||||
|
||||
-- Возвращает адрес и порт соединения.
|
||||
socket:get_address() --> str, int
|
||||
|
||||
-- Возвращает состояние NoDelay
|
||||
socket:is_nodelay() --> bool
|
||||
|
||||
-- Устанавливает состояние NoDelay
|
||||
socket:set_nodelay(state: bool)
|
||||
```
|
||||
|
||||
```lua
|
||||
|
||||
@ -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
|
||||
player.set_spawnpoint(playerid: int, x: number, y: number, z: 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 если игроков нет.
|
||||
|
||||
@ -37,6 +37,7 @@ document["worlds-panel"]:clear()
|
||||
| Название | Тип | Чтение | Запись | Описание |
|
||||
| ------------- | ------- | ------ | ------ | ----------------------------------------- |
|
||||
| id | string | да | *нет* | идентификатор элемента |
|
||||
| exists | bool | да | *нет* | проверяет, существует ли элемент |
|
||||
| pos | vec2 | да | да | позиция элемента внутри контейнера |
|
||||
| wpos | vec2 | да | да | позиция элемента в окне |
|
||||
| size | vec2 | да | да | размер элемента |
|
||||
@ -196,6 +197,10 @@ document["worlds-panel"]:clear()
|
||||
| data:update() | применяет изменения и загружает холст в видеопамять |
|
||||
| data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
|
||||
| data:create_texture(name: str) | создаёт и делится текстурой с рендерером |
|
||||
| data:unbind_texture() | отвязывает текстуру от холста |
|
||||
| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст |
|
||||
| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету |
|
||||
| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету |
|
||||
|
||||
## Рамка встраивания (iframe)
|
||||
|
||||
|
||||
@ -42,6 +42,8 @@
|
||||
|
||||
- `from` - точка начала примитива. Пример: `from (0,0,0)`
|
||||
- `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` - отображаемая текстура для всех сторон по-умолчанию.
|
||||
- `shading` определяет возможность затенения на примитиве. Пример: `shading off`
|
||||
- `delete` удаляет стороны по именам (top, bottom, east, west, north, south)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "base",
|
||||
"title": "Base",
|
||||
"version": "0.29",
|
||||
"version": "0.30",
|
||||
"description": "basic content package"
|
||||
}
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
function run_script(path)
|
||||
debug.log("starting application script "..path)
|
||||
|
||||
local code = file.read(path)
|
||||
local chunk, err = loadstring(code, path)
|
||||
if chunk == nil then
|
||||
error(err)
|
||||
end
|
||||
setfenv(chunk, setmetatable({app=__vc_app}, {__index=_G}))
|
||||
start_coroutine(chunk, path)
|
||||
__vc_start_app_script(path)
|
||||
end
|
||||
|
||||
function refresh()
|
||||
document.list:clear()
|
||||
|
||||
|
||||
local allpacks = table.merge(pack.get_available(), pack.get_installed())
|
||||
local infos = pack.get_info(allpacks)
|
||||
for _, name in ipairs(allpacks) do
|
||||
|
||||
@ -24,10 +24,46 @@ function update_setting(x, id, name, postfix)
|
||||
)
|
||||
end
|
||||
|
||||
local initialized = false
|
||||
|
||||
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-regular", "Regular Sounds", 0.01)
|
||||
create_setting("audio.volume-ui", "UI Sounds", 0.01)
|
||||
create_setting("audio.volume-ambient", "Ambient", 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
|
||||
|
||||
@ -53,12 +53,22 @@ function create_checkbox(id, name, tooltip)
|
||||
))
|
||||
end
|
||||
|
||||
|
||||
function on_open()
|
||||
create_setting("camera.fov", "FOV", 1, "°")
|
||||
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.inertia", "Camera Inertia")
|
||||
create_checkbox("camera.fov-effects", "Camera FOV Effects")
|
||||
create_checkbox("display.limit-fps-iconified", "Limit Background FPS")
|
||||
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
|
||||
end
|
||||
|
||||
@ -39,8 +39,9 @@ function on_open()
|
||||
create_setting("chunks.load-distance", "Load Distance", 1)
|
||||
create_setting("chunks.load-speed", "Load Speed", 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.soft-lighting", "Soft lighting", "graphics.soft-lighting.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.ssao", "SSAO", "graphics.ssao.tooltip")
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<container id="%{id}" size="32" tooltip="%{text}"
|
||||
onclick="events.emit('core:open_traceback', '%{traceback}')">
|
||||
<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"
|
||||
onclick="document['%{id}']:destruct()"></image>
|
||||
</container>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
onclick='%{open_func}("%{filename}")'
|
||||
markup='md'
|
||||
tooltip='%{unit}'
|
||||
sizefunc="-1,-1">
|
||||
size-func="-1,-1">
|
||||
[#FFFFFF80]%{path}[#FFFFFFFF]%{name}
|
||||
</label>
|
||||
</container>
|
||||
|
||||
63
res/modules/internal/audio_input.lua
Normal file
63
res/modules/internal/audio_input.lua
Normal 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
|
||||
@ -14,6 +14,8 @@ FFI.cdef[[
|
||||
|
||||
local malloc = FFI.C.malloc
|
||||
local free = FFI.C.free
|
||||
local FFIBytearray
|
||||
local bytearray_type
|
||||
|
||||
local function grow_buffer(self, elems)
|
||||
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
||||
@ -119,6 +121,23 @@ local function get_capacity(self)
|
||||
return self.capacity
|
||||
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 = {
|
||||
append=append,
|
||||
insert=insert,
|
||||
@ -127,6 +146,7 @@ local bytearray_methods = {
|
||||
clear=clear,
|
||||
reserve=reserve,
|
||||
get_capacity=get_capacity,
|
||||
slice=slice,
|
||||
}
|
||||
|
||||
local bytearray_mt = {
|
||||
@ -168,9 +188,9 @@ local bytearray_mt = {
|
||||
}
|
||||
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)
|
||||
local t = type(n)
|
||||
if t == "string" then
|
||||
@ -210,7 +230,59 @@ local function FFIBytearray_as_string(bytes)
|
||||
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 {
|
||||
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
|
||||
FFIBytearray_as_string = FFIBytearray_as_string
|
||||
FFIBytearray_as_string = FFIBytearray_as_string,
|
||||
FFIU16view = FFIU16view,
|
||||
FFII16view = FFII16view,
|
||||
FFIU32view = FFIU32view,
|
||||
FFII32view = FFII32view,
|
||||
}
|
||||
|
||||
146
res/modules/internal/debugging.lua
Normal file
146
res/modules/internal/debugging.lua
Normal 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
|
||||
48
res/modules/internal/deprecated.lua
Normal file
48
res/modules/internal/deprecated.lua
Normal 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
|
||||
@ -2,7 +2,14 @@ local events = {
|
||||
handlers = {}
|
||||
}
|
||||
|
||||
local __parse_path = parse_path
|
||||
local __pack_is_installed = pack.is_installed
|
||||
|
||||
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
|
||||
events.handlers[event] = {}
|
||||
end
|
||||
|
||||
45
res/modules/internal/extensions/file.lua
Normal file
45
res/modules/internal/extensions/file.lua
Normal 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
|
||||
71
res/modules/internal/extensions/inventory.lua
Normal file
71
res/modules/internal/extensions/inventory.lua
Normal 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
|
||||
37
res/modules/internal/extensions/math.lua
Normal file
37
res/modules/internal/extensions/math.lua
Normal 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
|
||||
13
res/modules/internal/extensions/pack.lua
Normal file
13
res/modules/internal/extensions/pack.lua
Normal 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
|
||||
126
res/modules/internal/extensions/string.lua
Normal file
126
res/modules/internal/extensions/string.lua
Normal 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
|
||||
179
res/modules/internal/extensions/table.lua
Normal file
179
res/modules/internal/extensions/table.lua
Normal 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
|
||||
70
res/modules/internal/rules.lua
Normal file
70
res/modules/internal/rules.lua
Normal 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
|
||||
@ -44,6 +44,8 @@ local Socket = {__index={
|
||||
is_alive=function(self) return network.__is_alive(self.id) end,
|
||||
is_connected=function(self) return network.__is_connected(self.id) end,
|
||||
get_address=function(self) return network.__get_address(self.id) end,
|
||||
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={
|
||||
|
||||
@ -105,82 +105,10 @@ elseif __vc_app then
|
||||
complete_app_lib(__vc_app)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
if enable_experimental then
|
||||
require "core:internal/maths_inline"
|
||||
end
|
||||
|
||||
require "core:internal/maths_inline"
|
||||
require "core:internal/debugging"
|
||||
require "core:internal/audio_input"
|
||||
require "core:internal/extensions/inventory"
|
||||
asserts = require "core:internal/asserts"
|
||||
events = require "core:internal/events"
|
||||
|
||||
@ -188,6 +116,19 @@ function pack.unload(prefix)
|
||||
events.remove_by_prefix(prefix)
|
||||
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"
|
||||
|
||||
Document = gui_util.Document
|
||||
@ -283,11 +224,6 @@ entities.get_all = function(uids)
|
||||
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"
|
||||
|
||||
file.open = require "core:internal/stream_providers/file"
|
||||
@ -307,86 +243,20 @@ else
|
||||
os.pid = ffi.C.getpid()
|
||||
end
|
||||
|
||||
ffi = nil
|
||||
__vc_lock_internal_modules()
|
||||
|
||||
math.randomseed(time.uptime() * 1536227939)
|
||||
|
||||
rules = {nexid = 1, rules = {}}
|
||||
rules = require "core:internal/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()
|
||||
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-content-access", hud._is_content_access(), function(value)
|
||||
hud._set_content_access(value)
|
||||
_rules.create("allow-content-access", _hud_is_content_access(), function(value)
|
||||
_hud_set_content_access(value)
|
||||
end)
|
||||
_rules.create("allow-flight", true, function(value)
|
||||
input.set_enabled("player.flight", value)
|
||||
@ -407,7 +277,7 @@ function __vc_on_hud_open()
|
||||
input.set_enabled("player.fast_interaction", value)
|
||||
end)
|
||||
_rules.create("allow-debug-cheats", true, function(value)
|
||||
hud._set_debug_cheats(value)
|
||||
_hud_set_debug_cheats(value)
|
||||
end)
|
||||
input.add_callback("devtools.console", function()
|
||||
if menu.page ~= "" then
|
||||
@ -427,7 +297,9 @@ function __vc_on_hud_open()
|
||||
end)
|
||||
input.add_callback("key:escape", function()
|
||||
if menu.page ~= "" then
|
||||
menu:reset()
|
||||
if not menu:back() then
|
||||
menu:reset()
|
||||
end
|
||||
elseif hud.is_inventory_open() then
|
||||
hud.close_inventory()
|
||||
else
|
||||
@ -562,6 +434,9 @@ end
|
||||
|
||||
local __post_runnables = {}
|
||||
|
||||
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
|
||||
audio.__reset_fetch_buffer = nil
|
||||
|
||||
function __process_post_runnables()
|
||||
if #__post_runnables then
|
||||
for _, func in ipairs(__post_runnables) do
|
||||
@ -587,6 +462,8 @@ function __process_post_runnables()
|
||||
__vc_named_coroutines[name] = nil
|
||||
end
|
||||
|
||||
fn_audio_reset_fetch_buffer()
|
||||
debug.pull_events()
|
||||
network.__process_events()
|
||||
block.__process_register_events()
|
||||
block.__perform_ticks(time.delta())
|
||||
@ -605,6 +482,7 @@ local _getinfo = debug.getinfo
|
||||
for i, name in ipairs(removed_names) do
|
||||
debug[name] = nil
|
||||
end
|
||||
|
||||
debug.getinfo = function(lvl, fields)
|
||||
if type(lvl) == "number" then
|
||||
lvl = lvl + 1
|
||||
@ -614,51 +492,7 @@ debug.getinfo = function(lvl, fields)
|
||||
return debuginfo
|
||||
end
|
||||
|
||||
-- --------- Deprecated functions ------ --
|
||||
local function wrap_deprecated(func, name, alternatives)
|
||||
return function (...)
|
||||
on_deprecated_call(name, alternatives)
|
||||
return func(...)
|
||||
end
|
||||
end
|
||||
require "core:internal/deprecated"
|
||||
|
||||
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
|
||||
ffi = nil
|
||||
__vc_lock_internal_modules()
|
||||
|
||||
@ -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
|
||||
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
|
||||
local _debug_getinfo = debug.getinfo
|
||||
|
||||
function crc32(bytes, chksum)
|
||||
local chksum = chksum or 0
|
||||
chksum = chksum or 0
|
||||
|
||||
local length = #bytes
|
||||
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)
|
||||
end
|
||||
|
||||
function pack.is_installed(packid)
|
||||
return file.isfile(packid..":package.json")
|
||||
-- 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 _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
|
||||
|
||||
function pack.data_file(packid, name)
|
||||
file.mkdirs("world:data/"..packid)
|
||||
return "world:data/"..packid.."/"..name
|
||||
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
|
||||
|
||||
function pack.shared_file(packid, name)
|
||||
file.mkdirs("config:"..packid)
|
||||
return "config:"..packid.."/"..name
|
||||
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
|
||||
|
||||
function timeit(iters, func, ...)
|
||||
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()
|
||||
local frames = 1
|
||||
while true do
|
||||
@ -603,15 +230,25 @@ function require(path)
|
||||
return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env)
|
||||
end
|
||||
|
||||
function __scripts_cleanup()
|
||||
function __scripts_cleanup(non_reset_packs)
|
||||
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
|
||||
local packname, _ = parse_path(k)
|
||||
if table.has(non_reset_packs, packname) then
|
||||
goto continue
|
||||
end
|
||||
if packname ~= "core" then
|
||||
debug.log("unloaded "..k)
|
||||
__cached_scripts[k] = nil
|
||||
package.loaded[k] = nil
|
||||
end
|
||||
__vc__pack_envs[packname] = nil
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
@ -619,7 +256,7 @@ function __vc__error(msg, frame, n, lastn)
|
||||
if events then
|
||||
local frames = debug.get_traceback(1)
|
||||
events.emit(
|
||||
"core:error", msg,
|
||||
"core:error", msg,
|
||||
table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
|
||||
)
|
||||
end
|
||||
@ -633,42 +270,20 @@ function __vc_warning(msg, detail, n)
|
||||
end
|
||||
end
|
||||
|
||||
function file.name(path)
|
||||
return path:match("([^:/\\]+)$")
|
||||
end
|
||||
require "core:internal/extensions/pack"
|
||||
require "core:internal/extensions/math"
|
||||
require "core:internal/extensions/file"
|
||||
require "core:internal/extensions/table"
|
||||
require "core:internal/extensions/string"
|
||||
|
||||
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
|
||||
local bytearray = require "core:internal/bytearray"
|
||||
Bytearray = bytearray.FFIBytearray
|
||||
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
||||
U16view = bytearray.FFIU16view
|
||||
I16view = bytearray.FFII16view
|
||||
U32view = bytearray.FFIU32view
|
||||
I32view = bytearray.FFII32view
|
||||
Bytearray_construct = function(...) return Bytearray(...) end
|
||||
|
||||
bit.compile = require "core:bitwise/compiler"
|
||||
bit.execute = require "core:bitwise/executor"
|
||||
|
||||
@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдаць дазвол на мады
|
||||
Error at line %{0}=Памылка ў радку %{0}
|
||||
Run=Запусціць
|
||||
Filter=Фільтр
|
||||
Are you sure you want to open the link: =Ці вы ўпэўненыя, што хочаце адкрыць спасылку:
|
||||
|
||||
editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць
|
||||
devtools.traceback=Стэк выклікаў (ад апошняга)
|
||||
|
||||
@ -7,6 +7,7 @@ Back=Zurück
|
||||
Continue=Weitermachen
|
||||
Add=Hinzufügen
|
||||
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.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden
|
||||
|
||||
@ -20,6 +20,7 @@ devtools.output=Output
|
||||
graphics.gamma.tooltip=Lighting brightness curve
|
||||
graphics.backlight.tooltip=Backlight to prevent total darkness
|
||||
graphics.dense-render.tooltip=Enables transparency in blocks like leaves
|
||||
graphics.soft-lighting.tooltip=Enables blocks soft lighting
|
||||
|
||||
# settings
|
||||
settings.Controls Search Mode=Search by attached button name
|
||||
|
||||
@ -19,6 +19,7 @@ Problems=Ongelmia
|
||||
Monitor=Valvonta
|
||||
Debug=Virheenkorjaus
|
||||
File=Tiedosto
|
||||
Are you sure you want to open the link: =Haluatko varmasti avata linkin:
|
||||
|
||||
devtools.traceback=Puhelupino (viimeisestä)
|
||||
error.pack-not-found=Pakettia ei löytynyt!
|
||||
|
||||
@ -7,6 +7,7 @@ Back=Powrót
|
||||
Continue=Kontynuacja
|
||||
Add=Dodać
|
||||
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
|
||||
|
||||
|
||||
@ -25,6 +25,8 @@ Grant %{0} pack modification permission?=Выдать разрешение на
|
||||
Error at line %{0}=Ошибка на строке %{0}
|
||||
Run=Запустить
|
||||
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 - Повторить
|
||||
devtools.traceback=Стек вызовов (от последнего)
|
||||
@ -38,7 +40,8 @@ pack.remove-confirm=Удалить весь поставляемый паком/
|
||||
# Подсказки
|
||||
graphics.gamma.tooltip=Кривая яркости освещения
|
||||
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
|
||||
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья.
|
||||
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья
|
||||
graphics.soft-lighting.tooltip=Включает мягкое освещение у блоков
|
||||
|
||||
# Меню
|
||||
menu.Apply=Применить
|
||||
@ -79,6 +82,7 @@ world.delete-confirm=Удалить мир безвозвратно?
|
||||
settings.Ambient=Фон
|
||||
settings.Backlight=Подсветка
|
||||
settings.Dense blocks render=Плотный рендер блоков
|
||||
settings.Soft lighting=Мягкое освещение
|
||||
settings.Camera Shaking=Тряска Камеры
|
||||
settings.Camera Inertia=Инерция Камеры
|
||||
settings.Camera FOV Effects=Эффекты поля зрения
|
||||
@ -102,6 +106,9 @@ settings.Limit Background FPS=Ограничить фоновую частоту
|
||||
settings.Advanced render=Продвинутый рендер
|
||||
settings.Shadows quality=Качество теней
|
||||
settings.Conflict=Найдены возможные конфликты
|
||||
settings.Windowed=Оконный
|
||||
settings.Borderless=Безрамочный
|
||||
settings.Microphone=Микрофон
|
||||
|
||||
# Управление
|
||||
chunks.reload=Перезагрузить Чанки
|
||||
|
||||
@ -23,6 +23,7 @@ devtools.traceback=Стек викликів (від останнього)
|
||||
error.pack-not-found=Не вдалося знайти пакет
|
||||
error.dependency-not-found=Використовувана залежність не знайдена
|
||||
pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)?
|
||||
Are you sure you want to open the link: =Ви впевнені, що хочете відкрити посилання:
|
||||
|
||||
# Меню
|
||||
menu.Apply=Застосувати
|
||||
|
||||
@ -24,6 +24,7 @@ Save=Saqlash
|
||||
Grant %{0} pack modification permission?=%{0} to‘plamini o‘zgartirish ruxsatini berilsinmi?
|
||||
Error at line %{0}=%{0}-qatorida xatolik
|
||||
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
|
||||
devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab)
|
||||
|
||||
@ -91,7 +91,9 @@ target_compile_options(
|
||||
/wd4245 # conversion from 'int' to 'const size_t', signed/unsigned
|
||||
# mismatch
|
||||
/wd4100 # unreferenced formal parameter
|
||||
/wd4457 # declaration of 'var' hides function parameter
|
||||
/wd4458 # declaration of 'var' hides class member
|
||||
/wd4459 # declaration of 'var' hides global declaration
|
||||
/wd4101 # 'var': unreferenced local variable
|
||||
/wd4388 # 'token' : signed/unsigned mismatch
|
||||
/wd4018 # '>': signed/unsigned mismatch
|
||||
@ -101,6 +103,7 @@ target_compile_options(
|
||||
-Wextra
|
||||
# additional warnings
|
||||
-Wformat-nonliteral
|
||||
#-Wsign-conversion
|
||||
-Wcast-align
|
||||
-Wpointer-arith
|
||||
-Wundef
|
||||
@ -108,6 +111,10 @@ target_compile_options(
|
||||
-Wno-unused-parameter
|
||||
-Wno-sign-compare
|
||||
-Wno-unknown-pragmas
|
||||
>
|
||||
$<$<CXX_COMPILER_ID:Clang>:
|
||||
-Wduplicated-branches
|
||||
-Wduplicated-cond
|
||||
>)
|
||||
|
||||
target_link_options(
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#include "content/Content.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
#include "coders/vec3.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "frontend/UiDocument.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
@ -184,7 +184,7 @@ assetload::postfunc assetload::atlas(
|
||||
if (!append_atlas(builder, file)) continue;
|
||||
}
|
||||
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) {
|
||||
atlas->prepare();
|
||||
assets->store(std::unique_ptr<Atlas>(atlas), name);
|
||||
@ -501,7 +501,7 @@ static bool load_animation(
|
||||
}
|
||||
if (!append_atlas(builder, file)) continue;
|
||||
}
|
||||
auto srcAtlas = builder.build(2, true);
|
||||
auto srcAtlas = builder.build(ATLAS_EXTRUSION, true);
|
||||
if (frameList.empty()) {
|
||||
for (const auto& frameName : builder.getNames()) {
|
||||
frameList.emplace_back(frameName, 0);
|
||||
|
||||
@ -5,11 +5,41 @@
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "alutil.hpp"
|
||||
#include "../MemoryPCMStream.hpp"
|
||||
|
||||
static debug::Logger logger("al-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(
|
||||
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;
|
||||
}
|
||||
|
||||
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(
|
||||
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++) {
|
||||
uint free_buffer = al->getFreeBuffer();
|
||||
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);
|
||||
}
|
||||
@ -130,11 +225,11 @@ void ALStream::unqueueBuffers(uint alsource) {
|
||||
uint ALStream::enqueueBuffers(uint alsource) {
|
||||
uint preloaded = 0;
|
||||
if (!unusedBuffers.empty()) {
|
||||
uint first_buffer = unusedBuffers.front();
|
||||
if (preloadBuffer(first_buffer, loop)) {
|
||||
uint firstBuffer = unusedBuffers.front();
|
||||
if (preloadBuffer(firstBuffer, loop)) {
|
||||
preloaded++;
|
||||
unusedBuffers.pop();
|
||||
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer));
|
||||
AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
|
||||
}
|
||||
}
|
||||
return preloaded;
|
||||
@ -144,14 +239,14 @@ void ALStream::update(double delta) {
|
||||
if (this->speaker == 0) {
|
||||
return;
|
||||
}
|
||||
auto p_speaker = audio::get_speaker(this->speaker);
|
||||
if (p_speaker == nullptr) {
|
||||
auto speaker = audio::get_speaker(this->speaker);
|
||||
if (speaker == nullptr) {
|
||||
this->speaker = 0;
|
||||
return;
|
||||
}
|
||||
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker);
|
||||
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
|
||||
assert(alspeaker != nullptr);
|
||||
if (alspeaker->stopped) {
|
||||
if (alspeaker->manuallyStopped) {
|
||||
this->speaker = 0;
|
||||
return;
|
||||
}
|
||||
@ -162,11 +257,11 @@ void ALStream::update(double delta) {
|
||||
uint preloaded = enqueueBuffers(alsource);
|
||||
|
||||
// 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) {
|
||||
p_speaker->play();
|
||||
} else {
|
||||
p_speaker->stop();
|
||||
speaker->play();
|
||||
} else if (isStopOnEnd()){
|
||||
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)
|
||||
: al(al), priority(priority), channel(channel), source(source) {
|
||||
}
|
||||
@ -273,7 +376,7 @@ void ALSpeaker::setLoop(bool loop) {
|
||||
|
||||
void ALSpeaker::play() {
|
||||
paused = false;
|
||||
stopped = false;
|
||||
manuallyStopped = false;
|
||||
auto p_channel = get_channel(this->channel);
|
||||
AL_CHECK(alSourcef(
|
||||
source,
|
||||
@ -289,7 +392,7 @@ void ALSpeaker::pause() {
|
||||
}
|
||||
|
||||
void ALSpeaker::stop() {
|
||||
stopped = true;
|
||||
manuallyStopped = true;
|
||||
if (source) {
|
||||
AL_CHECK(alSourceStop(source));
|
||||
|
||||
@ -353,6 +456,13 @@ int ALSpeaker::getPriority() const {
|
||||
return priority;
|
||||
}
|
||||
|
||||
|
||||
bool ALSpeaker::isManuallyStopped() const {
|
||||
return manuallyStopped;
|
||||
}
|
||||
|
||||
static bool alc_enumeration_ext = false;
|
||||
|
||||
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||
: device(device), context(context) {
|
||||
ALCint size;
|
||||
@ -365,9 +475,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||
maxSources = attrs[i + 1];
|
||||
}
|
||||
}
|
||||
auto devices = getAvailableDevices();
|
||||
logger.info() << "devices:";
|
||||
for (auto& name : devices) {
|
||||
auto outputDevices = getOutputDeviceNames();
|
||||
logger.info() << "output devices:";
|
||||
for (auto& name : outputDevices) {
|
||||
logger.info() << " " << name;
|
||||
}
|
||||
|
||||
auto inputDevices = getInputDeviceNames();
|
||||
logger.info() << "input devices:";
|
||||
for (auto& name : inputDevices) {
|
||||
logger.info() << " " << name;
|
||||
}
|
||||
}
|
||||
@ -385,8 +501,10 @@ ALAudio::~ALAudio() {
|
||||
AL_CHECK(alDeleteBuffers(1, &buffer));
|
||||
}
|
||||
|
||||
AL_CHECK(alcMakeContextCurrent(context));
|
||||
alcMakeContextCurrent(nullptr);
|
||||
check_alc_errors(device, "alcMakeContextCurrent");
|
||||
alcDestroyContext(context);
|
||||
check_alc_errors(device, "alcDestroyContext");
|
||||
if (!alcCloseDevice(device)) {
|
||||
logger.error() << "device not closed!";
|
||||
}
|
||||
@ -411,7 +529,71 @@ std::unique_ptr<Stream> ALAudio::openStream(
|
||||
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() {
|
||||
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
|
||||
|
||||
ALCdevice* device = alcOpenDevice(nullptr);
|
||||
if (device == nullptr) return nullptr;
|
||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||
@ -468,24 +650,6 @@ void ALAudio::freeBuffer(uint 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(
|
||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||
) {
|
||||
|
||||
@ -58,6 +58,7 @@ namespace audio {
|
||||
bool keepSource;
|
||||
char buffer[BUFFER_SIZE];
|
||||
bool loop = false;
|
||||
bool stopOnEnd = false;
|
||||
|
||||
bool preloadBuffer(uint buffer, bool loop);
|
||||
void unqueueBuffers(uint alsource);
|
||||
@ -80,6 +81,39 @@ namespace audio {
|
||||
void setTime(duration_t time) override;
|
||||
|
||||
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
|
||||
@ -90,7 +124,7 @@ namespace audio {
|
||||
float volume = 0.0f;
|
||||
public:
|
||||
ALStream* stream = nullptr;
|
||||
bool stopped = true;
|
||||
bool manuallyStopped = true;
|
||||
bool paused = false;
|
||||
uint source;
|
||||
duration_t duration = 0.0f;
|
||||
@ -130,6 +164,8 @@ namespace audio {
|
||||
bool isRelative() const override;
|
||||
|
||||
int getPriority() const override;
|
||||
|
||||
bool isManuallyStopped() const override;
|
||||
};
|
||||
|
||||
class ALAudio : public Backend {
|
||||
@ -152,15 +188,24 @@ namespace audio {
|
||||
void freeSource(uint source);
|
||||
void freeBuffer(uint buffer);
|
||||
|
||||
std::vector<std::string> getAvailableDevices() const;
|
||||
|
||||
std::unique_ptr<Sound> createSound(
|
||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||
) override;
|
||||
|
||||
std::unique_ptr<Stream> openStream(
|
||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
) 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(
|
||||
glm::vec3 position,
|
||||
glm::vec3 velocity,
|
||||
|
||||
67
src/audio/MemoryPCMStream.cpp
Normal file
67
src/audio/MemoryPCMStream.cpp
Normal 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();
|
||||
}
|
||||
44
src/audio/MemoryPCMStream.hpp
Normal file
44
src/audio/MemoryPCMStream.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
@ -61,6 +61,13 @@ namespace audio {
|
||||
|
||||
void setTime(duration_t time) override {
|
||||
}
|
||||
|
||||
bool isStopOnEnd() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void setStopOnEnd(bool stopOnEnd) override {
|
||||
}
|
||||
};
|
||||
|
||||
class NoAudio : public Backend {
|
||||
@ -71,10 +78,24 @@ namespace audio {
|
||||
std::unique_ptr<Sound> createSound(
|
||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||
) override;
|
||||
|
||||
std::unique_ptr<Stream> openStream(
|
||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
) 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(
|
||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||
) override {
|
||||
|
||||
@ -151,6 +151,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static std::unique_ptr<InputDevice> input_device = nullptr;
|
||||
|
||||
void audio::initialize(bool enabled, AudioSettings& settings) {
|
||||
enabled = enabled && settings.enabled.get();
|
||||
if (enabled) {
|
||||
@ -180,6 +182,15 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
|
||||
audio::get_channel(channel.name)->setVolume(value * value);
|
||||
}, 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) {
|
||||
@ -242,6 +253,38 @@ std::unique_ptr<Stream> audio::open_stream(
|
||||
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(
|
||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
|
||||
) {
|
||||
@ -421,8 +464,15 @@ void audio::update(double delta) {
|
||||
speaker->update(channel);
|
||||
}
|
||||
if (speaker->isStopped()) {
|
||||
streams.erase(it->first);
|
||||
it = speakers.erase(it);
|
||||
auto foundStream = streams.find(it->first);
|
||||
if (foundStream == streams.end() ||
|
||||
(!speaker->isManuallyStopped() &&
|
||||
foundStream->second->isStopOnEnd())) {
|
||||
streams.erase(it->first);
|
||||
it = speakers.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
@ -458,6 +508,9 @@ void audio::reset_channel(int index) {
|
||||
}
|
||||
|
||||
void audio::close() {
|
||||
if (input_device) {
|
||||
input_device->stopCapture();
|
||||
}
|
||||
speakers.clear();
|
||||
delete backend;
|
||||
backend = nullptr;
|
||||
|
||||
@ -24,6 +24,8 @@ namespace audio {
|
||||
/// @brief streams and important sounds
|
||||
constexpr inline int PRIORITY_HIGH = 10;
|
||||
|
||||
constexpr inline size_t MAX_INPUT_SAMPLES = 22050;
|
||||
|
||||
class Speaker;
|
||||
|
||||
/// @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
|
||||
class PCMStream {
|
||||
public:
|
||||
@ -121,6 +148,10 @@ namespace audio {
|
||||
/// (always equals bufferSize if seekable and looped)
|
||||
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;
|
||||
|
||||
/// @brief Close stream
|
||||
@ -195,6 +226,9 @@ namespace audio {
|
||||
/// @brief Set playhead to the selected time
|
||||
/// @param time selected time
|
||||
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
|
||||
@ -329,6 +363,8 @@ namespace audio {
|
||||
inline bool isStopped() const {
|
||||
return getState() == State::stopped;
|
||||
}
|
||||
|
||||
virtual bool isManuallyStopped() const = 0;
|
||||
};
|
||||
|
||||
class Backend {
|
||||
@ -341,12 +377,20 @@ namespace audio {
|
||||
virtual std::unique_ptr<Stream> openStream(
|
||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
) = 0;
|
||||
virtual std::unique_ptr<InputDevice> openInputDevice(
|
||||
const std::string& deviceName,
|
||||
uint sampleRate,
|
||||
uint channels,
|
||||
uint bitsPerSample
|
||||
) = 0;
|
||||
virtual void setListener(
|
||||
glm::vec3 position,
|
||||
glm::vec3 velocity,
|
||||
glm::vec3 lookAt,
|
||||
glm::vec3 up
|
||||
) = 0;
|
||||
virtual std::vector<std::string> getInputDeviceNames() = 0;
|
||||
virtual std::vector<std::string> getOutputDeviceNames() = 0;
|
||||
virtual void update(double delta) = 0;
|
||||
|
||||
/// @brief Check if backend is an abstraction that does not internally
|
||||
@ -402,6 +446,28 @@ namespace audio {
|
||||
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
|
||||
/// @param position listener position
|
||||
/// @param velocity listener velocity (used for Doppler effect)
|
||||
@ -515,6 +581,8 @@ namespace audio {
|
||||
/// @brief Stop all playing audio in channel, reset channel state
|
||||
void reset_channel(int channel);
|
||||
|
||||
InputDevice* get_input_device();
|
||||
|
||||
/// @brief Finalize audio system
|
||||
void close();
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "coders/json.hpp"
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "vcm.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "xml.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 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] {};
|
||||
regions[0].scale(to.x - from.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] {};
|
||||
enabled[i] = true;
|
||||
auto& mesh = model.addMesh(texfaces[i], shading);
|
||||
mesh.addBox(center, halfsize, regions, enabled);
|
||||
mesh.addBox(center, halfsize, regions, enabled, tsf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
inline constexpr int ENGINE_VERSION_MAJOR = 0;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 29;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 30;
|
||||
|
||||
#ifdef NDEBUG
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
||||
#endif // NDEBUG
|
||||
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.29";
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.30";
|
||||
|
||||
/// @brief world regions format version
|
||||
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 ATLAS_EXTRUSION = 2;
|
||||
|
||||
inline const std::string SHADERS_FOLDER = "shaders";
|
||||
inline const std::string TEXTURES_FOLDER = "textures";
|
||||
inline const std::string FONTS_FOLDER = "fonts";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include "ContentControl.hpp"
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "Content.hpp"
|
||||
#include "ContentPack.hpp"
|
||||
#include "ContentBuilder.hpp"
|
||||
@ -30,6 +30,7 @@ ContentControl::ContentControl(
|
||||
manager->setSources({
|
||||
"world:content",
|
||||
"user:content",
|
||||
"project:content",
|
||||
"res:content",
|
||||
});
|
||||
}
|
||||
@ -48,10 +49,10 @@ std::vector<std::string>& ContentControl::getBasePacks() {
|
||||
return basePacks;
|
||||
}
|
||||
|
||||
void ContentControl::resetContent() {
|
||||
void ContentControl::resetContent(const std::vector<std::string>& nonReset) {
|
||||
paths.setCurrentWorldFolder("");
|
||||
|
||||
scripting::cleanup();
|
||||
scripting::cleanup(nonReset);
|
||||
std::vector<PathsRoot> resRoots;
|
||||
{
|
||||
auto pack = ContentPack::createCore();
|
||||
@ -78,8 +79,6 @@ void ContentControl::loadContent(const std::vector<std::string>& names) {
|
||||
}
|
||||
|
||||
void ContentControl::loadContent() {
|
||||
scripting::cleanup();
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto& pack : contentPacks) {
|
||||
names.push_back(pack.id);
|
||||
|
||||
@ -34,7 +34,7 @@ public:
|
||||
std::vector<std::string>& getBasePacks();
|
||||
|
||||
/// @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);
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
#include "objects/rigging.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
|
||||
static debug::Logger logger("content-loader");
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
#include "coders/json.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "coders/commons.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include "../ContentPack.hpp"
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "world/generator/GeneratorDef.hpp"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#include "content/Content.hpp"
|
||||
#include "content/ContentBuilder.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
|
||||
315
src/devtools/DebuggingServer.cpp
Normal file
315
src/devtools/DebuggingServer.cpp
Normal 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;
|
||||
}
|
||||
106
src/devtools/DebuggingServer.hpp
Normal file
106
src/devtools/DebuggingServer.hpp
Normal 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
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
#include "Editor.hpp"
|
||||
|
||||
#include "engine/Engine.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "coders/syntax_parser.hpp"
|
||||
#include "SyntaxProcessor.hpp"
|
||||
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
#include "Project.hpp"
|
||||
|
||||
#include "data/dv_util.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/path.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
static debug::Logger logger("project");
|
||||
|
||||
Project::~Project() = default;
|
||||
|
||||
dv::value Project::serialize() const {
|
||||
@ -18,3 +23,23 @@ void Project::deserialize(const dv::value& src) {
|
||||
src.at("title").get(title);
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces/Process.hpp"
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
namespace scripting {
|
||||
@ -15,9 +16,13 @@ struct Project : Serializable {
|
||||
std::string title;
|
||||
std::vector<std::string> basePacks;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
std::unique_ptr<Process> setupCoroutine;
|
||||
|
||||
~Project();
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
void loadProjectClientScript();
|
||||
void loadProjectStartScript();
|
||||
};
|
||||
|
||||
15
src/engine/CoreParameters.hpp
Normal file
15
src/engine/CoreParameters.hpp
Normal 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;
|
||||
};
|
||||
@ -8,36 +8,36 @@
|
||||
#include "assets/AssetsLoader.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "coders/GLSLExtension.hpp"
|
||||
#include "coders/imageio.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
#include "coders/commons.hpp"
|
||||
#include "devtools/Editor.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "devtools/DebuggingServer.hpp"
|
||||
#include "content/ContentControl.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/settings_io.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
#include "frontend/screens/Screen.hpp"
|
||||
#include "graphics/render/ModelsGenerator.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "graphics/ui/elements/Menu.hpp"
|
||||
#include "logic/EngineController.hpp"
|
||||
#include "logic/CommandsInterpreter.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/scripting/scripting_hud.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "Mainloop.hpp"
|
||||
#include "ServerMainloop.hpp"
|
||||
#include "WindowControl.hpp"
|
||||
#include "EnginePaths.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
@ -48,29 +48,6 @@
|
||||
|
||||
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;
|
||||
|
||||
@ -85,7 +62,7 @@ Engine& Engine::getInstance() {
|
||||
|
||||
void Engine::onContentLoad() {
|
||||
editor->loadTools();
|
||||
langs::setup(langs::get_current(), paths.resPaths.collectRoots());
|
||||
langs::setup(langs::get_current(), paths->resPaths.collectRoots());
|
||||
|
||||
if (isHeadless()) {
|
||||
return;
|
||||
@ -106,26 +83,9 @@ void Engine::onContentLoad() {
|
||||
}
|
||||
|
||||
void Engine::initializeClient() {
|
||||
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]";
|
||||
}
|
||||
auto [window, input] = Window::initialize(&settings.display, title);
|
||||
if (!window || !input){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
window->setFramerate(settings.display.framerate.get());
|
||||
windowControl = std::make_unique<WindowControl>(*this);
|
||||
auto [window, input] = windowControl->initialize();
|
||||
|
||||
time.set(window->time());
|
||||
if (auto icon = load_icon()) {
|
||||
icon->flipY();
|
||||
window->setIcon(icon.get());
|
||||
}
|
||||
this->window = std::move(window);
|
||||
this->input = std::move(input);
|
||||
|
||||
@ -135,10 +95,11 @@ void Engine::initializeClient() {
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
menus::create_version_label(*gui);
|
||||
}
|
||||
keepAlive(settings.display.fullscreen.observe(
|
||||
[this](bool value) {
|
||||
if (value != this->window->isFullscreen()) {
|
||||
this->window->toggleFullscreen();
|
||||
keepAlive(settings.display.windowMode.observe(
|
||||
[this](int value) {
|
||||
WindowMode mode = static_cast<WindowMode>(value);
|
||||
if (mode != this->window->getMode()) {
|
||||
this->window->setMode(mode);
|
||||
}
|
||||
},
|
||||
true
|
||||
@ -149,6 +110,14 @@ void Engine::initializeClient() {
|
||||
},
|
||||
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) {
|
||||
@ -157,23 +126,28 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
|
||||
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
|
||||
if (params.headless) {
|
||||
logger.info() << "headless mode is enabled";
|
||||
logger.info() << "engine runs in headless mode";
|
||||
}
|
||||
if (params.projectFolder.empty()) {
|
||||
params.projectFolder = params.resFolder;
|
||||
}
|
||||
paths.setResourcesFolder(params.resFolder);
|
||||
paths.setUserFilesFolder(params.userFolder);
|
||||
paths.setProjectFolder(params.projectFolder);
|
||||
paths.prepare();
|
||||
paths = std::make_unique<EnginePaths>(params);
|
||||
loadProject();
|
||||
|
||||
editor = std::make_unique<devtools::Editor>(*this);
|
||||
cmd = std::make_unique<cmd::CommandsInterpreter>();
|
||||
network = network::Network::create(settings.network);
|
||||
|
||||
if (!params.scriptFile.empty()) {
|
||||
paths.setScriptFolder(params.scriptFile.parent_path());
|
||||
if (!params.debugServerString.empty()) {
|
||||
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();
|
||||
|
||||
@ -188,7 +162,7 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
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();
|
||||
});
|
||||
scripting::initialize(this);
|
||||
@ -197,10 +171,13 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
gui->setPageLoader(scripting::create_page_loader());
|
||||
}
|
||||
keepAlive(settings.ui.language.observe([this](auto lang) {
|
||||
langs::setup(lang, paths.resPaths.collectRoots());
|
||||
langs::setup(lang, paths->resPaths.collectRoots());
|
||||
}, true));
|
||||
|
||||
project->clientScript = load_client_project_script();
|
||||
project->loadProjectStartScript();
|
||||
if (!params.headless) {
|
||||
project->loadProjectClientScript();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::loadSettings() {
|
||||
@ -230,25 +207,17 @@ void Engine::loadControls() {
|
||||
|
||||
void Engine::updateHotkeys() {
|
||||
if (input->jpressed(Keycode::F2)) {
|
||||
saveScreenshot();
|
||||
windowControl->saveScreenshot();
|
||||
}
|
||||
if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) &&
|
||||
input->jpressed(Keycode::U)) {
|
||||
gui->toggleDebug();
|
||||
}
|
||||
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() {
|
||||
if (params.headless) {
|
||||
ServerMainloop(*this).run();
|
||||
@ -261,6 +230,20 @@ void Engine::postUpdate() {
|
||||
network->update();
|
||||
postRunnables.run();
|
||||
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() {
|
||||
@ -272,14 +255,32 @@ void Engine::updateFrontend() {
|
||||
gui->postAct();
|
||||
}
|
||||
|
||||
void Engine::nextFrame() {
|
||||
window->setFramerate(
|
||||
window->isIconified() && settings.display.limitFpsIconified.get()
|
||||
? 20
|
||||
: settings.display.framerate.get()
|
||||
);
|
||||
window->swapBuffers();
|
||||
input->pollEvents();
|
||||
void Engine::nextFrame(bool waitForRefresh) {
|
||||
windowControl->nextFrame(waitForRefresh);
|
||||
}
|
||||
|
||||
void Engine::startPauseLoop() {
|
||||
bool initialCursorLocked = false;
|
||||
if (!isHeadless()) {
|
||||
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() {
|
||||
@ -294,7 +295,11 @@ void Engine::saveSettings() {
|
||||
io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler));
|
||||
if (!params.headless) {
|
||||
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";
|
||||
}
|
||||
audio::close();
|
||||
debuggingServer.reset();
|
||||
network.reset();
|
||||
clearKeepedObjects();
|
||||
project.reset();
|
||||
@ -340,17 +346,18 @@ void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
|
||||
|
||||
void Engine::loadAssets() {
|
||||
logger.info() << "loading assets";
|
||||
Shader::preprocessor->setPaths(&paths.resPaths);
|
||||
Shader::preprocessor->setPaths(&paths->resPaths);
|
||||
|
||||
auto content = this->content->get();
|
||||
|
||||
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);
|
||||
|
||||
// no need
|
||||
// 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) {
|
||||
auto task = loader.startTask([=](){});
|
||||
task->waitForEnd();
|
||||
@ -387,6 +394,7 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
}
|
||||
if (project->clientScript && this->screen) {
|
||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
||||
window->setShouldRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,11 +428,11 @@ Assets* Engine::getAssets() {
|
||||
}
|
||||
|
||||
EnginePaths& Engine::getPaths() {
|
||||
return paths;
|
||||
return *paths;
|
||||
}
|
||||
|
||||
ResPaths& Engine::getResPaths() {
|
||||
return paths.resPaths;
|
||||
return paths->resPaths;
|
||||
}
|
||||
|
||||
std::shared_ptr<Screen> Engine::getScreen() {
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "delegates.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "io/settings_io.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
#include "CoreParameters.hpp"
|
||||
#include "PostRunnables.hpp"
|
||||
#include "Time.hpp"
|
||||
#include "delegates.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Window;
|
||||
class Assets;
|
||||
class Level;
|
||||
class Screen;
|
||||
class ContentControl;
|
||||
class EngineController;
|
||||
class EnginePaths;
|
||||
class Input;
|
||||
class Level;
|
||||
class ResPaths;
|
||||
class Screen;
|
||||
class SettingsHandler;
|
||||
class Window;
|
||||
class WindowControl;
|
||||
struct Project;
|
||||
|
||||
namespace gui {
|
||||
@ -36,6 +38,7 @@ namespace network {
|
||||
|
||||
namespace devtools {
|
||||
class Editor;
|
||||
class DebuggingServer;
|
||||
}
|
||||
|
||||
class initialize_error : public std::runtime_error {
|
||||
@ -43,22 +46,12 @@ public:
|
||||
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
struct CoreParameters {
|
||||
bool headless = false;
|
||||
bool testMode = false;
|
||||
std::filesystem::path resFolder = "res";
|
||||
std::filesystem::path userFolder = ".";
|
||||
std::filesystem::path scriptFile;
|
||||
std::filesystem::path projectFolder;
|
||||
};
|
||||
|
||||
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
|
||||
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
CoreParameters params;
|
||||
EngineSettings settings;
|
||||
EnginePaths paths;
|
||||
|
||||
std::unique_ptr<EnginePaths> paths;
|
||||
std::unique_ptr<Project> project;
|
||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||
std::unique_ptr<Assets> assets;
|
||||
@ -71,6 +64,8 @@ class Engine : public util::ObjectsKeeper {
|
||||
std::unique_ptr<Input> input;
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
std::unique_ptr<devtools::Editor> editor;
|
||||
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
||||
std::unique_ptr<WindowControl> windowControl;
|
||||
PostRunnables postRunnables;
|
||||
Time time;
|
||||
OnWorldOpen levelConsumer;
|
||||
@ -101,9 +96,11 @@ public:
|
||||
|
||||
void postUpdate();
|
||||
|
||||
void applicationTick();
|
||||
void updateFrontend();
|
||||
void renderFrame();
|
||||
void nextFrame();
|
||||
void nextFrame(bool waitForRefresh);
|
||||
void startPauseLoop();
|
||||
|
||||
/// @brief Set screen (scene).
|
||||
/// nullptr may be used to delete previous screen before creating new one,
|
||||
@ -138,8 +135,6 @@ public:
|
||||
postRunnables.postRunnable(callback);
|
||||
}
|
||||
|
||||
void saveScreenshot();
|
||||
|
||||
EngineController* getController();
|
||||
|
||||
void setLevelConsumer(OnWorldOpen levelConsumer);
|
||||
@ -181,4 +176,10 @@ public:
|
||||
const Project& getProject() {
|
||||
return *project;
|
||||
}
|
||||
|
||||
devtools::DebuggingServer* getDebuggingServer() {
|
||||
return debuggingServer.get();
|
||||
}
|
||||
|
||||
void detachDebugger();
|
||||
};
|
||||
|
||||
@ -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 <array>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include <stdexcept>
|
||||
#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;
|
||||
|
||||
static debug::Logger logger("engine-paths");
|
||||
static std::random_device random_device;
|
||||
|
||||
static inline io::path SCREENSHOTS_FOLDER = "user:screenshots";
|
||||
static inline io::path CONTENT_FOLDER = "user:content";
|
||||
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("user", std::make_shared<io::StdfsDevice>(userFilesFolder));
|
||||
io::set_device("project", std::make_shared<io::StdfsDevice>(projectFolder));
|
||||
|
||||
if (!io::is_directory("res:")) {
|
||||
throw std::runtime_error(
|
||||
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() << "user files folder: " << fs::canonical(userFilesFolder).u8string();
|
||||
logger.info() << "project folder: " << fs::canonical(projectFolder).u8string();
|
||||
@ -57,15 +71,7 @@ void EnginePaths::prepare() {
|
||||
io::create_subdevice("config", "user", "config");
|
||||
}
|
||||
|
||||
const std::filesystem::path& EnginePaths::getUserFilesFolder() const {
|
||||
return userFilesFolder;
|
||||
}
|
||||
|
||||
const std::filesystem::path& EnginePaths::getResourcesFolder() const {
|
||||
return resourcesFolder;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) {
|
||||
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) const {
|
||||
auto folder = SCREENSHOTS_FOLDER;
|
||||
if (!io::is_directory(folder)) {
|
||||
io::create_directories(folder);
|
||||
@ -93,10 +99,6 @@ io::path EnginePaths::getWorldsFolder() const {
|
||||
return WORLDS_FOLDER;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getCurrentWorldFolder() {
|
||||
return currentWorldFolder;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getWorldFolderByName(const std::string& name) {
|
||||
return getWorldsFolder() / name;
|
||||
}
|
||||
@ -130,24 +132,6 @@ std::vector<io::path> EnginePaths::scanForWorlds() const {
|
||||
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) {
|
||||
if (folder.empty()) {
|
||||
io::remove_device("world");
|
||||
@ -1,15 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "CoreParameters.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include "io.hpp"
|
||||
#include "data/dv.hpp"
|
||||
|
||||
struct PathsRoot {
|
||||
std::string name;
|
||||
io::path path;
|
||||
@ -46,24 +46,13 @@ class EnginePaths {
|
||||
public:
|
||||
ResPaths resPaths;
|
||||
|
||||
void prepare();
|
||||
|
||||
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);
|
||||
EnginePaths(CoreParameters& params);
|
||||
|
||||
io::path getWorldFolderByName(const std::string& name);
|
||||
io::path getWorldsFolder() const;
|
||||
|
||||
void setCurrentWorldFolder(io::path folder);
|
||||
io::path getCurrentWorldFolder();
|
||||
io::path getNewScreenshotFile(const std::string& ext);
|
||||
io::path getNewScreenshotFile(const std::string& ext) const;
|
||||
|
||||
std::string mount(const io::path& file);
|
||||
void unmount(const std::string& name);
|
||||
@ -80,9 +69,9 @@ public:
|
||||
static inline io::path CONTROLS_FILE = "user:controls.toml";
|
||||
static inline io::path SETTINGS_FILE = "user:settings.toml";
|
||||
private:
|
||||
std::filesystem::path userFilesFolder {"."};
|
||||
std::filesystem::path resourcesFolder {"res"};
|
||||
std::filesystem::path projectFolder = resourcesFolder;
|
||||
std::filesystem::path resourcesFolder;
|
||||
std::filesystem::path userFilesFolder;
|
||||
std::filesystem::path projectFolder;
|
||||
io::path currentWorldFolder;
|
||||
std::optional<std::filesystem::path> scriptFolder;
|
||||
std::vector<PathsRoot> entryPoints;
|
||||
@ -18,6 +18,7 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
|
||||
void Mainloop::run() {
|
||||
auto& time = engine.getTime();
|
||||
auto& window = engine.getWindow();
|
||||
auto& settings = engine.getSettings();
|
||||
|
||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||
if (level == nullptr) {
|
||||
@ -38,13 +39,17 @@ void Mainloop::run() {
|
||||
logger.info() << "main loop started";
|
||||
while (!window.isShouldClose()){
|
||||
time.update(window.time());
|
||||
engine.applicationTick();
|
||||
engine.updateFrontend();
|
||||
|
||||
if (!window.isIconified()) {
|
||||
engine.renderFrame();
|
||||
}
|
||||
engine.postUpdate();
|
||||
engine.nextFrame();
|
||||
engine.nextFrame(
|
||||
settings.display.adaptiveFpsInMenu.get() &&
|
||||
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||
);
|
||||
}
|
||||
logger.info() << "main loop stopped";
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "ServerMainloop.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "EnginePaths.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "interfaces/Process.hpp"
|
||||
@ -15,8 +16,6 @@ using namespace std::chrono;
|
||||
|
||||
static debug::Logger logger("mainloop");
|
||||
|
||||
inline constexpr int TPS = 20;
|
||||
|
||||
ServerMainloop::ServerMainloop(Engine& engine) : engine(engine) {
|
||||
}
|
||||
|
||||
@ -34,12 +33,11 @@ void ServerMainloop::run() {
|
||||
setLevel(std::move(level));
|
||||
});
|
||||
|
||||
logger.info() << "starting test " << coreParams.scriptFile.string();
|
||||
auto process = scripting::start_coroutine(
|
||||
auto process = scripting::start_app_script(
|
||||
"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;
|
||||
auto begin = system_clock::now();
|
||||
auto startupTime = begin;
|
||||
@ -63,6 +61,7 @@ void ServerMainloop::run() {
|
||||
controller->getLevel()->getWorld()->updateTimers(delta);
|
||||
controller->update(glm::min(delta, 0.2), false);
|
||||
}
|
||||
engine.applicationTick();
|
||||
engine.postUpdate();
|
||||
|
||||
if (!coreParams.testMode) {
|
||||
|
||||
93
src/engine/WindowControl.cpp
Normal file
93
src/engine/WindowControl.cpp
Normal 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());
|
||||
}
|
||||
26
src/engine/WindowControl.hpp
Normal file
26
src/engine/WindowControl.hpp
Normal 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;
|
||||
};
|
||||
@ -49,8 +49,7 @@ LevelFrontend::LevelFrontend(
|
||||
auto sound = rassets.get<audio::Sound>(material->stepsSound);
|
||||
glm::vec3 pos {};
|
||||
auto soundsCamera = currentPlayer->currentCamera.get();
|
||||
if (soundsCamera == currentPlayer->spCamera.get() ||
|
||||
soundsCamera == currentPlayer->tpCamera.get()) {
|
||||
if (currentPlayer->isCurrentCameraBuiltin()) {
|
||||
soundsCamera = currentPlayer->fpCamera.get();
|
||||
}
|
||||
bool relative = player == currentPlayer &&
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "graphics/ui/elements/TextBox.hpp"
|
||||
#include "graphics/ui/elements/TrackBar.hpp"
|
||||
#include "graphics/ui/elements/InputBindBox.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
@ -43,10 +44,15 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
|
||||
return label;
|
||||
}
|
||||
|
||||
static bool should_keep_previous(GUI& gui) {
|
||||
return !gui.getInput().isCursorLocked();
|
||||
}
|
||||
|
||||
// TODO: move to xml
|
||||
// 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(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
@ -81,7 +87,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
fpsMax = fps;
|
||||
});
|
||||
|
||||
panel->listenInterval(1.0f, [&engine]() {
|
||||
panel->listenInterval(1.0f, [&engine, &gui]() {
|
||||
const auto& network = engine.getNetwork();
|
||||
size_t totalDownload = network.getTotalDownload();
|
||||
size_t totalUpload = network.getTotalUpload();
|
||||
@ -135,7 +141,16 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
std::to_wstring(player.getId());
|
||||
}));
|
||||
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;
|
||||
stream << "r:" << vox.state.rotation << " s:"
|
||||
<< 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 {
|
||||
const auto& selection = player.selection;
|
||||
// TODO: move to xml finally
|
||||
static CursorSelection prevSelection {};
|
||||
|
||||
auto selection = player.selection;
|
||||
const auto& vox = selection.vox;
|
||||
if (vox.id == BLOCK_VOID && should_keep_previous(gui)) {
|
||||
selection = prevSelection;
|
||||
} else {
|
||||
prevSelection = selection;
|
||||
}
|
||||
|
||||
if (vox.id == BLOCK_VOID) {
|
||||
return L"x: - y: - z: -";
|
||||
}
|
||||
@ -158,7 +182,16 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
L" z: " + std::to_wstring(selection.actualPosition.z);
|
||||
}));
|
||||
panel->add(create_label(gui, [&]() {
|
||||
// TODO: move to xml finally
|
||||
static entityid_t prevEid = ENTITY_NONE;
|
||||
|
||||
auto eid = player.getSelectedEntity();
|
||||
if (eid == ENTITY_NONE && should_keep_previous(gui)) {
|
||||
eid = prevEid;
|
||||
} else {
|
||||
prevEid = eid;
|
||||
}
|
||||
|
||||
if (eid == ENTITY_NONE) {
|
||||
return std::wstring {L"entity: -"};
|
||||
} else if (auto entity = level.entities->get(eid)) {
|
||||
|
||||
@ -469,6 +469,7 @@ void Hud::showExchangeSlot() {
|
||||
gui,
|
||||
SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr)
|
||||
);
|
||||
exchangeSlot->setId("hud.exchange-slot");
|
||||
exchangeSlot->bind(exchangeSlotInv->getId(), exchangeSlotInv->getSlot(0), &content);
|
||||
exchangeSlot->setColor(glm::vec4());
|
||||
exchangeSlot->setInteractive(false);
|
||||
@ -756,3 +757,13 @@ void Hud::setAllowPause(bool 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;
|
||||
}
|
||||
|
||||
@ -213,6 +213,8 @@ public:
|
||||
|
||||
void setAllowPause(bool flag);
|
||||
|
||||
bool isOpen(const std::string& layoutid) const;
|
||||
|
||||
static bool showGeneratorMinimap;
|
||||
|
||||
/// @brief Runtime updating debug visualization texture
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
#include "graphics/ui/elements/Menu.hpp"
|
||||
#include "graphics/ui/gui_util.hpp"
|
||||
#include "interfaces/Task.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "locale.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "screens/MenuScreen.hpp"
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "core_defs.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "assets/Assets.hpp"
|
||||
#include "frontend/ContentGfxCache.hpp"
|
||||
#include "frontend/LevelFrontend.hpp"
|
||||
@ -75,13 +76,14 @@ LevelScreen::LevelScreen(
|
||||
engine, *controller, *renderer, assets, *player
|
||||
);
|
||||
|
||||
keepAlive(settings.graphics.backlight.observe([=](bool) {
|
||||
player->chunks->saveAndClear();
|
||||
renderer->clear();
|
||||
}));
|
||||
keepAlive(settings.graphics.denseRender.observe([=](bool) {
|
||||
auto resetChunks = [=](bool) {
|
||||
player->chunks->saveAndClear();
|
||||
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();
|
||||
}));
|
||||
keepAlive(settings.camera.fov.observe([=](double value) {
|
||||
|
||||
@ -12,9 +12,11 @@
|
||||
#include "window/Camera.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
|
||||
MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
||||
uicamera =
|
||||
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y);
|
||||
MenuScreen::MenuScreen(Engine& engine)
|
||||
: Screen(engine),
|
||||
uicamera(
|
||||
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y)
|
||||
) {
|
||||
uicamera->perspective = false;
|
||||
uicamera->near = -1.0f;
|
||||
uicamera->far = 1.0f;
|
||||
@ -24,7 +26,7 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
||||
MenuScreen::~MenuScreen() = default;
|
||||
|
||||
void MenuScreen::onOpen() {
|
||||
engine.getContentControl().resetContent();
|
||||
engine.getContentControl().resetContent({});
|
||||
|
||||
auto menu = engine.getGUI().getMenu();
|
||||
menu->reset();
|
||||
|
||||
@ -8,21 +8,6 @@ inline constexpr glm::vec3 X(1, 0, 0);
|
||||
inline constexpr glm::vec3 Y(0, 1, 0);
|
||||
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(
|
||||
const glm::vec3& pos,
|
||||
const glm::vec3& right,
|
||||
@ -39,6 +24,23 @@ void Mesh::addPlane(
|
||||
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(
|
||||
const glm::vec3& pos,
|
||||
const glm::vec3& right,
|
||||
@ -56,14 +58,15 @@ void Mesh::addRect(
|
||||
}
|
||||
|
||||
void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) {
|
||||
addPlane(pos+Z*size, X*size, Y*size, Z);
|
||||
addPlane(pos-Z*size, -X*size, Y*size, -Z);
|
||||
UVRegion fullRegion (0, 0, 1, 1);
|
||||
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);
|
||||
addPlane(pos+Y*size, X*size, -Z*size, Y, fullRegion);
|
||||
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);
|
||||
addPlane(pos+X*size, -Z*size, Y*size, X, fullRegion);
|
||||
addPlane(pos-X*size, Z*size, Y*size, -X, fullRegion);
|
||||
}
|
||||
|
||||
void Mesh::addBox(
|
||||
@ -71,19 +74,29 @@ void Mesh::addBox(
|
||||
const glm::vec3& size,
|
||||
const UVRegion (&uvs)[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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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) {
|
||||
|
||||
@ -22,14 +22,16 @@ namespace model {
|
||||
const glm::vec3& pos,
|
||||
const glm::vec3& right,
|
||||
const glm::vec3& up,
|
||||
const glm::vec3& norm
|
||||
const glm::vec3& norm,
|
||||
const UVRegion& region
|
||||
);
|
||||
void addPlane(
|
||||
const glm::vec3& pos,
|
||||
const glm::vec3& right,
|
||||
const glm::vec3& up,
|
||||
const glm::vec3& norm,
|
||||
const UVRegion& region
|
||||
const UVRegion& region,
|
||||
const glm::mat4& transform
|
||||
);
|
||||
void addRect(
|
||||
const glm::vec3& pos,
|
||||
@ -45,6 +47,13 @@ namespace model {
|
||||
const UVRegion (&texfaces)[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);
|
||||
};
|
||||
|
||||
|
||||
@ -49,6 +49,14 @@ ImageData* Atlas::getImage() const {
|
||||
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) {
|
||||
entries.push_back(atlasentry{name, std::shared_ptr<ImageData>(image.release())});
|
||||
names.insert(name);
|
||||
|
||||
@ -14,8 +14,8 @@ class ImageData;
|
||||
class Texture;
|
||||
|
||||
class Atlas {
|
||||
std::unique_ptr<Texture> texture;
|
||||
std::unique_ptr<ImageData> image;
|
||||
std::shared_ptr<Texture> texture;
|
||||
std::shared_ptr<ImageData> image;
|
||||
std::unordered_map<std::string, UVRegion> regions;
|
||||
public:
|
||||
/// @param image atlas raster
|
||||
@ -36,6 +36,9 @@ public:
|
||||
|
||||
Texture* getTexture() const;
|
||||
ImageData* getImage() const;
|
||||
|
||||
std::shared_ptr<Texture> shareTexture() const;
|
||||
std::shared_ptr<ImageData> shareImageData() const;
|
||||
};
|
||||
|
||||
struct atlasentry {
|
||||
|
||||
@ -25,7 +25,7 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
|
||||
0,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
NULL
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user