Merge branch 'main' into volume-renderer

This commit is contained in:
MihailRis 2026-01-10 23:28:28 +03:00
commit 305f44d1c6
70 changed files with 802 additions and 580 deletions

View file

@ -1,4 +1,7 @@
# Block Models # Block Models (depricated)
> ![WARNING]
> The `model-primitives` property is deprecated. Use the [VCM format](vcm.md) for a textual description of the model's primitives.
Block model may be created with following properties: Block model may be created with following properties:

View file

@ -56,6 +56,11 @@ Enables translucency support in block textures (examples: water, ice).
Should only be used when needed, as it impacts performance. Should only be used when needed, as it impacts performance.
Not required for full transparency (grass, flowers). Not required for full transparency (grass, flowers).
### *solid*
Explicitly specifies that the block is solid - completely overlapping the blocks behind it.
Used by blocks with the `custom` model type, which must support culling of invisible geometry, as with the `block` type. If `false` specified then ignored.
### *rotation* ### *rotation*
Rotation profile (set of available block rotations and behaviour of placing block rotation) from list: Rotation profile (set of available block rotations and behaviour of placing block rotation) from list:

View file

@ -136,7 +136,9 @@ Set specified bits.
## Raycast ## Raycast
```lua ```lua
block.raycast(start: vec3, dir: vec3, max_distance: number, [optional] dest: table, [optional] filter: table) -> { block.raycast(start: vec3, dir: vec3, max_distance: number, [optional] dest: table,
[optional] filter: table, [optional] include_non_selectable = false
) -> {
block: int, -- block id block: int, -- block id
endpoint: vec3, -- point of the ray hit point endpoint: vec3, -- point of the ray hit point
iendpoint: vec3, -- position of the block hit by the ray iendpoint: vec3, -- position of the block hit by the ray
@ -150,6 +152,9 @@ Casts a ray from the start point in the direction of *dir*. Max_distance specifi
Argument `filter` can be used to tell ray what blocks can be skipped(passed through) during ray-casting. Argument `filter` can be used to tell ray what blocks can be skipped(passed through) during ray-casting.
To use filter `dest` argument must be filled with some value(can be nil), it's done for backwards compatability To use filter `dest` argument must be filled with some value(can be nil), it's done for backwards compatability
The `include_non_selectable` argument determines whether blocks that cannot be selected by the cursor will be included.
Example - `base:water`
The function returns a table with the results or nil if the ray does not hit any block. The function returns a table with the results or nil if the ray does not hit any block.
The result will use the destination table instead of creating a new one if the optional argument specified. The result will use the destination table instead of creating a new one if the optional argument specified.

View file

@ -63,8 +63,9 @@ entities.get_all_in_radius(center: vec3, radius: number) -> array<int>
``` ```
```lua ```lua
entities.raycast(start: vec3, dir: vec3, max_distance: number, entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [optional] destination: table,
ignore: int, [optional] destination: table, [optional] filter: table) -> table or nil [optional] filter: table, [optional] include_non_selectable = false
) -> table or nil
``` ```
The function is an extended version of [block.raycast](libblock.md#raycast). Returns a table with the results if the ray touches a block or entity. The function is an extended version of [block.raycast](libblock.md#raycast). Returns a table with the results if the ray touches a block or entity.

View file

@ -117,6 +117,7 @@ player.set_suspended(pid: int, suspended: bool)
Setter and getter for the player's suspended status. Setter and getter for the player's suspended status.
When suspended, the entity is deleted and the player is disabled from the world simulation. When suspended, the entity is deleted and the player is disabled from the world simulation.
When resumed, a new entity will be created with a different ID.
```lua ```lua
player.set_name(playerid: int, name: str) player.set_name(playerid: int, name: str)

View file

@ -1,4 +1,7 @@
# Модели блоков # Модели блоков (устарело)
> ![WARNING]
> Свойство `model-primitives` является устаревшим, используйте [формат VCM](vcm.md) для текстового описания модели из примитивов.
Создание собственной модели блока может быть реализовано при указании у блока свойств: Создание собственной модели блока может быть реализовано при указании у блока свойств:
```js ```js

View file

@ -57,6 +57,11 @@
Следует использовать только при надобности, так как влияет на производительность. Следует использовать только при надобности, так как влияет на производительность.
Не требуется для полной прозрачности (трава, цветы). Не требуется для полной прозрачности (трава, цветы).
### Цельность - *solid*
Явно указывает, что блок является цельным (полным) - полностью перекрывает блоки за собой.
Используется блоками с типом модели `custom`, которые должны поддерживать отсечение невидимой геометрии, как у типа `block`. Свойство игнорируется при указании `false`.
### Вращение - *rotation* ### Вращение - *rotation*
Профиль вращения (набор положений, в которые можно установить блок) из списка: Профиль вращения (набор положений, в которые можно установить блок) из списка:

View file

@ -92,7 +92,9 @@ block.material(blockid: int) -> string
## Raycast ## Raycast
```lua ```lua
block.raycast(start: vec3, dir: vec3, max_distance: number, [опционально] dest: table, [опционально] filter: table) -> { block.raycast(start: vec3, dir: vec3, max_distance: number, [опционально] dest: table,
[опционально] filter: table, [опционально] include_non_selectable = false
) -> {
block: int, -- id блока block: int, -- id блока
endpoint: vec3, -- точка касания луча endpoint: vec3, -- точка касания луча
iendpoint: vec3, -- позиция блока, которого касается луч iendpoint: vec3, -- позиция блока, которого касается луч
@ -106,6 +108,9 @@ block.raycast(start: vec3, dir: vec3, max_distance: number, [опциональ
Аргумент `filter` позволяет указать какие блоки являются "прозрачными" для луча, прим.: {"base:glass","base:water"}. Аргумент `filter` позволяет указать какие блоки являются "прозрачными" для луча, прим.: {"base:glass","base:water"}.
Для использования агрумент `dest` нужно чем-то заполнить(можно nil), это сделано для обратной совместимости Для использования агрумент `dest` нужно чем-то заполнить(можно nil), это сделано для обратной совместимости
Аргумент `include_non_selectable` определяет, будут ли учтены блоки, которые нельзя выбрать курсором.
Пример - `base:water`
Функция возвращает таблицу с результатами или nil, если луч не касается блока. Функция возвращает таблицу с результатами или nil, если луч не касается блока.
Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент. Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент.

View file

@ -62,8 +62,9 @@ entities.get_all_in_radius(center: vec3, radius: number) -> table<int>
``` ```
```lua ```lua
entities.raycast(start: vec3, dir: vec3, max_distance: number, entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [optional] destination: table,
ignore: int, [optional] destination: table, [optional] filter: table) -> table или nil [optional] filter: table, [optional] include_non_selectable = false
) -> table или nil
``` ```
Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности. Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности.

View file

@ -30,6 +30,7 @@ player.get_name(playerid: int) -> string
-- Сеттер и геттер статуса "заморозки" игрока. -- Сеттер и геттер статуса "заморозки" игрока.
-- При заморозке сущность игрока удаляется. При последующей разморозке будет создана новая, с другим id.
player.set_suspended(pid: int, suspended: boolean) player.set_suspended(pid: int, suspended: boolean)
player.is_suspended(pid: int) -> boolean player.is_suspended(pid: int) -> boolean
``` ```
@ -125,4 +126,4 @@ player.get_all() -> table<int>
-- Возвращает id ближайшего к указанной позиции игрока, либо nil если игроков нет. -- Возвращает id ближайшего к указанной позиции игрока, либо nil если игроков нет.
player.get_nearest(position: vec3) -> int / nil player.get_nearest(position: vec3) -> int / nil
``` ```

View file

@ -20,15 +20,32 @@
В отличие от XML в корне может находиться несколько элементов. В отличие от XML в корне может находиться несколько элементов.
На данный момент существует два вида примитивов: box и rect, а также, part (описывает часть примитива, такую как, например, сторона куба). На данный момент существует три вида примитивов: tri, box и rect, а также, part (описывает часть примитива, такую как, например, сторона куба).
> [!NOTE] > [!NOTE]
> По-умолчанию текстурные координаты определяются размерами примитивов. > По-умолчанию текстурные координаты определяются размерами примитивов.
> При размерах более 1.0 результат не определён и может измениться в последующих обновлениях. > При размерах более 1.0 результат не определён и может измениться в последующих обновлениях.
> Используйте `region` или `region-scale` для ручной настройки. > Используйте `region` или `region-scale` для ручной настройки.
### Свойства `tri`
Примитив описывает треугольник.
- `a` - точка A. Пример: `a (0,0,0)`
- `b` - точка B. Пример: `b (1,0,0)`
- `c` - точка С. Пример: `c (1,1,0)`
- `uv` - нормализованные текстурные координаты точек (6 чисел, по 2 на точку). Пример: `uv (0,0, 1,0, 1,1)`
- `texture` - отображаемая текстура. По-умолчанию: `$0`. Пример: `texture "blocks:sand"`
- `region` - UV регион в границах выбранной текстуры, определяется позициями противоположных углов квадрата нормализованных текстурных координат. Пример: `region (0,0,1,1)`
- `region-scale` вектор-множитель для UV региона. Пример: `region-scale (0.5,1)`
- `shading` определяет возможность затенения на примитиве. Пример: `shading off`
Текстурные координаты по-умолчанию не зависят от формы из размера, в отличие от других примитивов.
### Свойства `rect` ### Свойства `rect`
Примитив описывает параллелограмм через векторы right и up.
- `from` - точка начала примитива. Пример: `from (0,0.5,0.125)` - `from` - точка начала примитива. Пример: `from (0,0.5,0.125)`
- `right` - вектор X, также определяющий ширину примитива. Пример: `right (1,0,0)` - `right` - вектор X, также определяющий ширину примитива. Пример: `right (1,0,0)`
- `up` - вектор Y, также определяющий высоту примитива. Пример: `up (0,1,0)` - `up` - вектор Y, также определяющий высоту примитива. Пример: `up (0,1,0)`
@ -40,6 +57,8 @@
### Свойства `box` ### Свойства `box`
Примитив описывает прямоугольный параллелепипед.
- `from` - точка начала примитива. Пример: `from (0,0,0)` - `from` - точка начала примитива. Пример: `from (0,0,0)`
- `to` - противоположная от начала точка. Пример: `to (1,1,1)` - `to` - противоположная от начала точка. Пример: `to (1,1,1)`
- `origin` - точка, относительно которой будет применено вращение. По-умолчанию: центр примитива. Пример: `origin (0.5,0.5,0.5)` - `origin` - точка, относительно которой будет применено вращение. По-умолчанию: центр примитива. Пример: `origin (0.5,0.5,0.5)`
@ -48,7 +67,9 @@
- `shading` определяет возможность затенения на примитиве. Пример: `shading off` - `shading` определяет возможность затенения на примитиве. Пример: `shading off`
- `delete` удаляет стороны по именам (top, bottom, east, west, north, south) - `delete` удаляет стороны по именам (top, bottom, east, west, north, south)
### Свойства `part` (в `box`) ### Свойства `part` (вложен в `box`)
Примитив описывает свойства сторон прямоугольного параллелепипеда с указанными тегами (top/bottom/north/south/east/west).
- `tags` - определяет то, на какаие из сторон будут применены свойства. Пример: `tags (top,bottom)` - `tags` - определяет то, на какаие из сторон будут применены свойства. Пример: `tags (top,bottom)`
- `texture` - отображаемая текстура. По-умолчанию: `$индекс_стороны`. - `texture` - отображаемая текстура. По-умолчанию: `$индекс_стороны`.

View file

@ -15,7 +15,7 @@
<textbox id='search_textbox' multiline='false' size='440,25' sub-consumer='function(x) refresh_search() end'></textbox> <textbox id='search_textbox' multiline='false' size='440,25' sub-consumer='function(x) refresh_search() end'></textbox>
</panel> </panel>
<image onclick='core.open_folder("user:content")' interactive='true' src='gui/folder_icon' <image onclick='app.open_folder("user:content")' interactive='true' src='gui/folder_icon'
size='32' margin='0,0,18,66' gravity='bottom-right' size='32' margin='0,0,18,66' gravity='bottom-right'
color='#FFFFFF50' hover-color='#FFFFFF10'/> color='#FFFFFF50' hover-color='#FFFFFF10'/>
@ -38,4 +38,4 @@
<panel id='packs_cur' pos='15,34' size='440,455' color='0' max-length='455' scrollable='true'> <panel id='packs_cur' pos='15,34' size='440,455' color='0' max-length='455' scrollable='true'>
<!-- content is generated in script --> <!-- content is generated in script -->
</panel> </panel>
</container> </container>

View file

@ -9,7 +9,7 @@
<textbox id='search_textbox' multiline='false' size='440,25' sub-consumer='function(x) refresh_search() end'></textbox> <textbox id='search_textbox' multiline='false' size='440,25' sub-consumer='function(x) refresh_search() end'></textbox>
</panel> </panel>
<image onclick='core.open_folder("user:content")' interactive='true' src='gui/folder_icon' <image onclick='app.open_folder("user:content")' interactive='true' src='gui/folder_icon'
size='32' margin='0,0,10,15' gravity='bottom-right' size='32' margin='0,0,10,15' gravity='bottom-right'
color='#FFFFFF50' hover-color='#FFFFFF10'/> color='#FFFFFF50' hover-color='#FFFFFF10'/>

View file

@ -3,5 +3,5 @@
<button onclick='menu.page="scripts"'>@Scripts</button> <button onclick='menu.page="scripts"'>@Scripts</button>
<button onclick='menu.page="settings"'>@Settings</button> <button onclick='menu.page="settings"'>@Settings</button>
<button onclick='menu.page="content_menu"'>@Content</button> <button onclick='menu.page="content_menu"'>@Content</button>
<button onclick='core.quit()'>@Quit</button> <button onclick='app.quit(true)'>@Quit</button>
</panel> </panel>

View file

@ -21,7 +21,7 @@ function create_world()
local seed = document.seed_box.text local seed = document.seed_box.text
local generator = settings.generator local generator = settings.generator
session.reset_entry('new_world') session.reset_entry('new_world')
core.new_world(name, seed, generator) app.new_world(name, seed, generator)
end end
function world_name_placeholder() function world_name_placeholder()

View file

@ -2,7 +2,7 @@
<button onclick='menu:reset()'>@Continue</button> <button onclick='menu:reset()'>@Continue</button>
<button onclick='menu.page="content?mode=world"'>@Content</button> <button onclick='menu.page="content?mode=world"'>@Content</button>
<button onclick='menu.page="settings"'>@Settings</button> <button onclick='menu.page="settings"'>@Settings</button>
<button onclick='core.close_world(true)'> <button onclick='app.close_world(true)'>
@Save and Quit to Menu @Save and Quit to Menu
</button> </button>
</panel> </panel>

View file

@ -10,7 +10,7 @@
<panel margin='6' gravity='bottom-left' size='250' color='0' interval='1'> <panel margin='6' gravity='bottom-left' size='250' color='0' interval='1'>
<button onclick='menu.page="languages"' id='langs_btn'>-</button> <button onclick='menu.page="languages"' id='langs_btn'>-</button>
<button onclick='core.open_folder("user:")'>@Open data folder</button> <button onclick='app.open_folder("user:")'>@Open data folder</button>
<button id='s_rst' onclick='sections:set("reset")'>@Reset settings</button> <button id='s_rst' onclick='sections:set("reset")'>@Reset settings</button>
<button onclick='menu:back()'>@Back</button> <button onclick='menu:back()'>@Back</button>
</panel> </panel>

View file

@ -1,7 +1,7 @@
function on_open() function on_open()
document.langs_btn.text = string.format( document.langs_btn.text = string.format(
"%s: %s", gui.str("Language", "settings"), "%s: %s", gui.str("Language", "settings"),
gui.get_locales_info()[core.get_setting("ui.language")].name gui.get_locales_info()[app.get_setting("ui.language")].name
) )
sections = RadioGroup({ sections = RadioGroup({
audio=document.s_aud, audio=document.s_aud,

View file

@ -1,25 +1,25 @@
function create_setting(id, name, step, postfix) function create_setting(id, name, step, postfix)
local info = core.get_setting_info(id) local info = app.get_setting_info(id)
postfix = postfix or "" postfix = postfix or ""
document.root:add(gui.template("track_setting", { document.root:add(gui.template("track_setting", {
id=id, id=id,
name=gui.str(name, "settings"), name=gui.str(name, "settings"),
value=core.get_setting(id), value=app.get_setting(id),
min=info.min, min=info.min,
max=info.max, max=info.max,
step=step, step=step,
postfix=postfix postfix=postfix
})) }))
update_setting(core.get_setting(id), id, name, postfix) update_setting(app.get_setting(id), id, name, postfix)
end end
function update_setting(x, id, name, postfix) function update_setting(x, id, name, postfix)
core.set_setting(id, x) app.set_setting(id, x)
-- updating label -- updating label
document[id..".L"].text = string.format( document[id..".L"].text = string.format(
"%s: %s%s", "%s: %s%s",
gui.str(name, "settings"), gui.str(name, "settings"),
core.str_setting(id), app.str_setting(id),
postfix postfix
) )
end end
@ -54,7 +54,7 @@ function on_open()
create_setting("audio.volume-music", "Music", 0.01) create_setting("audio.volume-music", "Music", 0.01)
document.root:add("<label context='settings'>@Microphone</label>") document.root:add("<label context='settings'>@Microphone</label>")
document.root:add("<select id='input_device_select' ".. document.root:add("<select id='input_device_select' "..
"onselect='function(opt) core.set_setting(\"audio.input-device\", opt) end'/>") "onselect='function(opt) app.set_setting(\"audio.input-device\", opt) end'/>")
document.root:add("<container id='input_volume_outer' color='#000000' size='4'>" document.root:add("<container id='input_volume_outer' color='#000000' size='4'>"
.."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>" .."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>"
.."</container>") .."</container>")
@ -67,5 +67,8 @@ function on_open()
table.insert(devices, {value=name, text=name}) table.insert(devices, {value=name, text=name})
end end
selectbox.options = devices selectbox.options = devices
selectbox.value = audio.__get_input_info().device_specifier local info = audio.__get_input_info()
if info then
selectbox.value = info.device_specifier
end
end end

View file

@ -40,12 +40,12 @@ function refresh_sensitivity()
document.sensitivity_label.text = string.format( document.sensitivity_label.text = string.format(
"%s: %s", "%s: %s",
gui.str("Mouse Sensitivity", "settings"), gui.str("Mouse Sensitivity", "settings"),
core.str_setting("camera.sensitivity") app.str_setting("camera.sensitivity")
) )
end end
function change_sensitivity(val) function change_sensitivity(val)
core.set_setting("camera.sensitivity", val) app.set_setting("camera.sensitivity", val)
refresh_sensitivity() refresh_sensitivity()
end end
@ -93,7 +93,7 @@ function refresh_binding_marks()
end end
function on_open() function on_open()
document.sensitivity_track.value = core.get_setting("camera.sensitivity") document.sensitivity_track.value = app.get_setting("camera.sensitivity")
refresh_sensitivity() refresh_sensitivity()
local panel = document.bindings_panel local panel = document.bindings_panel

View file

@ -10,14 +10,14 @@ tostring_overrides["display.framerate"] = function(x)
end end
function create_setting(id, name, step, postfix, tooltip, changeonrelease) function create_setting(id, name, step, postfix, tooltip, changeonrelease)
local info = core.get_setting_info(id) local info = app.get_setting_info(id)
postfix = postfix or "" postfix = postfix or ""
tooltip = tooltip or "" tooltip = tooltip or ""
changeonrelease = changeonrelease or "" changeonrelease = changeonrelease or ""
document.root:add(gui.template("track_setting", { document.root:add(gui.template("track_setting", {
id=id, id=id,
name=gui.str(name, "settings"), name=gui.str(name, "settings"),
value=core.get_setting(id), value=app.get_setting(id),
min=info.min, min=info.min,
max=info.max, max=info.max,
step=step, step=step,
@ -25,7 +25,7 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease)
tooltip=tooltip, tooltip=tooltip,
changeonrelease=changeonrelease changeonrelease=changeonrelease
})) }))
update_setting(core.get_setting(id), id, name, postfix) update_setting(app.get_setting(id), id, name, postfix)
end end
function update_setting(x, id, name, postfix) function update_setting(x, id, name, postfix)
@ -34,13 +34,13 @@ function update_setting(x, id, name, postfix)
if func then if func then
str = func(x) str = func(x)
else else
str = core.str_setting(id) str = app.str_setting(id)
end end
-- updating label -- updating label
document[id..".L"].text = string.format( document[id..".L"].text = string.format(
"%s: %s%s", "%s: %s%s",
gui.str(name, "settings"), gui.str(name, "settings"),
str, str,
postfix postfix
) )
end end
@ -48,8 +48,8 @@ end
function create_checkbox(id, name, tooltip) function create_checkbox(id, name, tooltip)
tooltip = tooltip or '' tooltip = tooltip or ''
document.root:add(string.format( document.root:add(string.format(
"<checkbox consumer='function(x) core.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>", "<checkbox consumer='function(x) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
id, core.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
)) ))
end end
@ -59,11 +59,11 @@ function on_open()
create_setting("display.framerate", "Framerate", 1, "", "", true) create_setting("display.framerate", "Framerate", 1, "", "", true)
document.root:add(string.format( document.root:add(string.format(
"<select context='settings' onselect='function(opt) core.set_setting(\"display.window-mode\", tonumber(opt)) end' selected='%s'>".. "<select context='settings' onselect='function(opt) app.set_setting(\"display.window-mode\", tonumber(opt)) end' selected='%s'>"..
"<option value='0'>@Windowed</option>".. "<option value='0'>@Windowed</option>"..
"<option value='1'>@Fullscreen</option>".. "<option value='1'>@Fullscreen</option>"..
"<option value='2'>@Borderless</option>".. "<option value='2'>@Borderless</option>"..
"</select>", core.get_setting("display.window-mode")) "</select>", app.get_setting("display.window-mode"))
) )
create_checkbox("camera.shaking", "Camera Shaking") create_checkbox("camera.shaking", "Camera Shaking")

View file

@ -1,12 +1,12 @@
function create_setting(id, name, step, postfix, tooltip, changeonrelease) function create_setting(id, name, step, postfix, tooltip, changeonrelease)
local info = core.get_setting_info(id) local info = app.get_setting_info(id)
postfix = postfix or "" postfix = postfix or ""
tooltip = tooltip or "" tooltip = tooltip or ""
changeonrelease = changeonrelease or "" changeonrelease = changeonrelease or ""
document.root:add(gui.template("track_setting", { document.root:add(gui.template("track_setting", {
id=id, id=id,
name=gui.str(name, "settings"), name=gui.str(name, "settings"),
value=core.get_setting(id), value=app.get_setting(id),
min=info.min, min=info.min,
max=info.max, max=info.max,
step=step, step=step,
@ -14,15 +14,15 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease)
tooltip=tooltip, tooltip=tooltip,
changeonrelease=changeonrelease changeonrelease=changeonrelease
})) }))
update_setting(core.get_setting(id), id, name, postfix) update_setting(app.get_setting(id), id, name, postfix)
end end
function update_setting(x, id, name, postfix) function update_setting(x, id, name, postfix)
-- updating label -- updating label
document[id..".L"].text = string.format( document[id..".L"].text = string.format(
"%s: %s%s", "%s: %s%s",
gui.str(name, "settings"), gui.str(name, "settings"),
core.str_setting(id), app.str_setting(id),
postfix postfix
) )
end end
@ -30,8 +30,8 @@ end
function create_checkbox(id, name, tooltip) function create_checkbox(id, name, tooltip)
tooltip = tooltip or '' tooltip = tooltip or ''
document.root:add(string.format( document.root:add(string.format(
"<checkbox consumer='function(x) core.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>", "<checkbox consumer='function(x) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
id, core.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
)) ))
end end

View file

@ -11,7 +11,7 @@ function reset(category)
end end
function reset_setting(name) function reset_setting(name)
core.set_setting(name, core.get_setting_info(name).def) app.set_setting(name, app.get_setting_info(name).def)
end end
function reset_audio() function reset_audio()

View file

@ -3,6 +3,6 @@
<panel id='worlds' size='390,1' padding='5' color='#FFFFFF11' max-length='400'> <panel id='worlds' size='390,1' padding='5' color='#FFFFFF11' max-length='400'>
</panel> </panel>
<button pos='485,525' size='500,40' onclick='core.open_folder("user:worlds")'>@Open worlds folder</button> <button pos='485,525' size='500,40' onclick='app.open_folder("user:worlds")'>@Open worlds folder</button>
<button onclick='menu:back()'>@Back</button> <button onclick='menu:back()'>@Back</button>
</panel> </panel>

View file

@ -1,7 +1,7 @@
function on_open() function on_open()
local worlds = world.get_list() local worlds = world.get_list()
for _, info in ipairs(worlds) do for _, info in ipairs(worlds) do
local major, minor = core.get_version() local major, minor = app.get_version()
if info.version[1] > major or info.version[2] > minor then if info.version[1] > major or info.version[2] > minor then
info.versionColor = "#A02010" info.versionColor = "#A02010"
else else

View file

@ -1,4 +1,4 @@
<button <button
onclick='core.set_setting("ui.language", "%{id}") menu:back()'> onclick='app.set_setting("ui.language", "%{id}") menu:back()'>
%{name} %{name}
</button> </button>

View file

@ -2,6 +2,6 @@
<label id='%{id}.L' margin='0,3,0,0'>%{name}: %{value}%{postfix}</label> <label id='%{id}.L' margin='0,3,0,0'>%{name}: %{value}%{postfix}</label>
<trackbar <trackbar
value='%{value}' min='%{min}' max='%{max}' step='%{step}' tooltip='%{tooltip}' value='%{value}' min='%{min}' max='%{max}' step='%{step}' tooltip='%{tooltip}'
consumer='function(x) core.set_setting("%{id}", x) end' change-on-release='%{changeonrelease}' consumer='function(x) app.set_setting("%{id}", x) end' change-on-release='%{changeonrelease}'
sub-consumer='function(x) update_setting(x, "%{id}", "%{name}", "%{postfix}") end'/> sub-consumer='function(x) update_setting(x, "%{id}", "%{name}", "%{postfix}") end'/>
</panel> </panel>

View file

@ -2,7 +2,7 @@
size='380,66' size='380,66'
color='#0F1E2DB2' color='#0F1E2DB2'
hover-color='#162B3399' hover-color='#162B3399'
onclick='core.open_world("%{name}")' onclick='app.open_world("%{name}")'
> >
<image pos='1' src='%{icon}' size='96,64'></image> <image pos='1' src='%{icon}' size='96,64'></image>
<label pos='104,8'>%{name}</label> <label pos='104,8'>%{name}</label>
@ -11,7 +11,7 @@
color='#00000000' color='#00000000'
hover-color='#FFFFFF2B' hover-color='#FFFFFF2B'
padding='0' padding='0'
onclick='core.delete_world("%{name}")'> onclick='app.delete_world("%{name}")'>
<image src='gui/delete_icon' size='32,32' color='#FFFFFF50'/> <image src='gui/delete_icon' size='32,32' color='#FFFFFF50'/>
</button> </button>
<label color='%{versionColor}' pos='317,44'>%{versionString}</label> <label color='%{versionColor}' pos='317,44'>%{versionString}</label>

View file

@ -34,7 +34,7 @@ function gui_util.load_page(query)
local filename = file.find(string.format("layouts/pages/%s.xml", name)) local filename = file.find(string.format("layouts/pages/%s.xml", name))
if filename then if filename then
name = file.prefix(filename)..":pages/"..name name = file.prefix(filename)..":pages/"..name
gui.load_document(filename, name, args) gui.load_document(filename, name, args, { app = __vc_app })
return name return name
end end
end end
@ -52,19 +52,21 @@ end
-- class designed for simple UI-nodes access via properties syntax -- class designed for simple UI-nodes access via properties syntax
local Element = {} local Element = {}
local __gui_getattr = gui.getattr
local __gui_setattr = gui.setattr
function Element.new(docname, name) function Element.new(docname, name)
return setmetatable({docname=docname, name=name}, { return setmetatable({docname=docname, name=name}, {
__index=function(self, k) __index=function(self, k)
return gui.getattr(self.docname, self.name, k) return __gui_getattr(self.docname, self.name, k)
end, end,
__newindex=function(self, k, v) __newindex=function(self, k, v)
gui.setattr(self.docname, self.name, k, v) __gui_setattr(self.docname, self.name, k, v)
end, end,
__ipairs=function(self) __ipairs=function(self)
local i = 0 local i = 0
return function() return function()
i = i + 1 i = i + 1
local elem = gui.getattr(self.docname, self.name, i) local elem = __gui_getattr(self.docname, self.name, i)
if elem == nil then if elem == nil then
return return
end end
@ -102,8 +104,8 @@ function RadioGroup:set(key)
end end
function RadioGroup:__call(elements, onset, default) function RadioGroup:__call(elements, onset, default)
local group = setmetatable({ local group = setmetatable({
elements=elements, elements=elements,
callback=onset, callback=onset,
current=nil current=nil
}, {__index=self}) }, {__index=self})
group:set(default) group:set(default)
@ -161,7 +163,7 @@ function gui.show_input_dialog(text, actual_callback, validator, confirm_text)
callback=callback, callback=callback,
validator=validator or function() return #text > 0 end validator=validator or function() return #text > 0 end
}) })
input.add_callback("key:escape", callback, gui.root[id]) input.add_callback("key:escape", function() gui.root[id]:destruct() end, gui.root[id])
input.add_callback("key:enter", callback, gui.root[id]) input.add_callback("key:enter", callback, gui.root[id])
gui.root[id.."_input"].focused = true gui.root[id.."_input"].focused = true
end end

View file

@ -2,5 +2,6 @@
name = "default" name = "default"
base_packs = ["base"] base_packs = ["base"]
permissions = [ permissions = [
"network" "network",
"record-audio"
] ]

View file

@ -58,7 +58,15 @@ local function process_player_inputs(pid, rot, delta)
end end
end end
local prepared = false
body:set_enabled(false)
function on_physics_update(delta) function on_physics_update(delta)
if not prepared then
prepared = true
body:set_enabled(true)
end
local pid = entity:get_player() local pid = entity:get_player()
if pid == -1 then if pid == -1 then
return return

View file

@ -29,7 +29,7 @@ local function configure_SSAO()
end end
events.on("core:setting.graphics.ssao.set", update_ssao_quality) events.on("core:setting.graphics.ssao.set", update_ssao_quality)
update_ssao_quality(core.get_setting("graphics.ssao")) update_ssao_quality(__vc_app.get_setting("graphics.ssao"))
end end
local function update_hand() local function update_hand()

View file

@ -7,7 +7,7 @@ local names = {
"hidden", "draw-group", "picking-item", "surface-replacement", "script-name", "hidden", "draw-group", "picking-item", "surface-replacement", "script-name",
"ui-layout", "inventory-size", "tick-interval", "overlay-texture", "ui-layout", "inventory-size", "tick-interval", "overlay-texture",
"translucent", "fields", "particles", "icon-type", "icon", "placing-block", "translucent", "fields", "particles", "icon-type", "icon", "placing-block",
"stack-size", "name", "script-file", "culling" "stack-size", "name", "script-file", "culling", "solid"
} }
for name, _ in pairs(user_props) do for name, _ in pairs(user_props) do
table.insert(names, name) table.insert(names, name)

View file

@ -1,4 +1,4 @@
local enable_experimental = core.get_setting("debug.enable-experimental") local enable_experimental = __vc_app.get_setting("debug.enable-experimental")
------------------------------------------------ ------------------------------------------------
------ Extended kit of standard functions ------ ------ Extended kit of standard functions ------
@ -24,41 +24,48 @@ function tb_frame_tostring(frame)
return s return s
end end
local core_set_setting = core.set_setting local __vc__app_script_coroutine
function core.set_setting(name, value, ...)
core_set_setting(name, value, ...)
events.emit("core:setting."..name..".set", value)
end
local function complete_app_lib(app) local function complete_app_lib(app)
local __app_load_content = app.load_content
local __app_reset_content = app.reset_content
local __app_reconfig_packs = app.reconfig_packs
local __app_tick = coroutine.yield
local __app_set_setting = app.set_setting
local __app_quit = app.quit
app.sleep = sleep app.sleep = sleep
app.script = __VC_SCRIPT_NAME app.script = __VC_SCRIPT_NAME
app.new_world = core.new_world
app.open_world = core.open_world app.set_setting = function(name, value, ...)
app.save_world = core.save_world __app_set_setting(name, value, ...)
app.close_world = core.close_world events.emit("core:setting."..name..".set", value)
app.reopen_world = core.reopen_world
app.delete_world = core.delete_world
app.reconfig_packs = core.reconfig_packs
app.get_setting = core.get_setting
app.set_setting = core.set_setting
app.tick = function()
coroutine.yield()
end end
app.get_version = core.get_version app.tick = __app_tick
app.get_setting_info = core.get_setting_info
app.load_content = function() local function call_in_app_script_co(func, ...)
core.load_content() local running = coroutine.running()
app.tick() if not running or running ~= __vc__app_script_coroutine then
error("content must be reload in application script coroutine")
end
func(...)
__app_tick()
end end
app.reset_content = core.reset_content
app.is_content_loaded = core.is_content_loaded app.reconfig_packs = function(...)
app.set_title = core.set_title call_in_app_script_co(__app_reconfig_packs, ...)
end
app.load_content = function(...)
call_in_app_script_co(__app_load_content, ...)
end
app.reset_content = function(...)
call_in_app_script_co(__app_reset_content, ...)
end
function app.config_packs(packs_list) function app.config_packs(packs_list)
-- Check if packs are valid and add dependencies to the configuration -- Check if packs are valid and add dependencies to the configuration
packs_list = pack.assemble(packs_list) packs_list = pack.assemble(packs_list)
local installed = pack.get_installed() local installed = pack.get_installed()
local toremove = {} local toremove = {}
for _, packid in ipairs(installed) do for _, packid in ipairs(installed) do
@ -75,15 +82,16 @@ local function complete_app_lib(app)
app.reconfig_packs(toadd, toremove) app.reconfig_packs(toadd, toremove)
end end
function app.quit() function app.quit(silent)
local tb = debug.get_traceback(1) if not silent then
local s = "app.quit() traceback:" local tb = debug.get_traceback(1)
for i, frame in ipairs(tb) do local s = "app.quit() traceback:"
s = s .. "\n\t"..tb_frame_tostring(frame) for i, frame in ipairs(tb) do
s = s .. "\n\t"..tb_frame_tostring(frame)
end
debug.log(s)
end end
debug.log(s) __app_quit()
core.quit()
coroutine.yield()
end end
function app.sleep_until(predicate, max_ticks, max_time) function app.sleep_until(predicate, max_ticks, max_time)
@ -123,6 +131,66 @@ function pack.unload(prefix)
events.remove_by_prefix(prefix) events.remove_by_prefix(prefix)
end end
local __vc_coroutines = {}
local __vc_named_coroutines = {}
local __vc_next_coroutine = 1
function __vc_start_coroutine(chunk)
local co = coroutine.create(chunk)
local id = __vc_next_coroutine
__vc_next_coroutine = __vc_next_coroutine + 1
__vc_coroutines[id] = co
return id
end
function __vc_resume_coroutine(id)
local co = __vc_coroutines[id]
if co then
local success, err = coroutine.resume(co)
if not success then
debug.error(err)
error(err)
end
return coroutine.status(co) ~= "dead"
end
return false
end
function __vc_stop_coroutine(id)
local co = __vc_coroutines[id]
if co then
if coroutine.close then
coroutine.close(co)
end
__vc_coroutines[id] = nil
end
end
function start_coroutine(chunk, name)
local co = coroutine.create(function()
local status, error = xpcall(chunk, function(err)
local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback()
if hud then
gui.alert(fullmsg, function()
if world.is_open() then
__vc_app.close_world()
else
__vc_app.reset_content()
menu:reset()
menu.page = "main"
end
end)
end
return fullmsg
end)
if not status then
debug.error(error)
end
end)
__vc_named_coroutines[name] = co
end
function __vc_start_app_script(path, name) function __vc_start_app_script(path, name)
debug.log("starting application script "..path) debug.log("starting application script "..path)
@ -134,9 +202,13 @@ function __vc_start_app_script(path, name)
local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) local script_env = setmetatable({app = app or __vc_app}, {__index=_G})
chunk = setfenv(chunk, script_env) chunk = setfenv(chunk, script_env)
if name then if name then
return start_coroutine(chunk, name) start_coroutine(chunk, name)
__vc__app_script_coroutine = __vc_named_coroutines[name]
return
else else
return __vc_start_coroutine(chunk) local id = __vc_start_coroutine(chunk)
__vc__app_script_coroutine = __vc_coroutines[id]
return id
end end
end end
@ -435,66 +507,6 @@ function __vc_on_world_quit()
file.__close_all_descriptors() file.__close_all_descriptors()
end end
local __vc_coroutines = {}
local __vc_named_coroutines = {}
local __vc_next_coroutine = 1
function __vc_start_coroutine(chunk)
local co = coroutine.create(chunk)
local id = __vc_next_coroutine
__vc_next_coroutine = __vc_next_coroutine + 1
__vc_coroutines[id] = co
return id
end
function __vc_resume_coroutine(id)
local co = __vc_coroutines[id]
if co then
local success, err = coroutine.resume(co)
if not success then
debug.error(err)
error(err)
end
return coroutine.status(co) ~= "dead"
end
return false
end
function __vc_stop_coroutine(id)
local co = __vc_coroutines[id]
if co then
if coroutine.close then
coroutine.close(co)
end
__vc_coroutines[id] = nil
end
end
function start_coroutine(chunk, name)
local co = coroutine.create(function()
local status, error = xpcall(chunk, function(err)
local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback()
if hud then
gui.alert(fullmsg, function()
if world.is_open() then
__vc_app.close_world()
else
__vc_app.reset_content()
menu:reset()
menu.page = "main"
end
end)
end
return fullmsg
end)
if not status then
debug.error(error)
end
end)
__vc_named_coroutines[name] = co
end
local __post_runnables = {} local __post_runnables = {}
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer

View file

@ -24,6 +24,7 @@ namespace {
std::vector<std::unique_ptr<Channel>> channels; std::vector<std::unique_ptr<Channel>> channels;
util::ObjectsKeeper objects_keeper {}; util::ObjectsKeeper objects_keeper {};
std::unique_ptr<InputDevice> input_device = nullptr; std::unique_ptr<InputDevice> input_device = nullptr;
static bool input_enabled = false;
} }
Channel::Channel(std::string name) : name(std::move(name)) { Channel::Channel(std::string name) : name(std::move(name)) {
@ -152,7 +153,9 @@ public:
} }
}; };
void audio::initialize(bool enabled, AudioSettings& settings) { void audio::initialize(
bool enabled, bool inputEnabled, AudioSettings& settings
) {
enabled = enabled && settings.enabled.get(); enabled = enabled && settings.enabled.get();
if (enabled) { if (enabled) {
logger.info() << "initializing ALAudio backend"; logger.info() << "initializing ALAudio backend";
@ -182,7 +185,10 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
}, true)); }, true));
} }
::input_device = backend->openInputDevice("", 44100, 1, 16); if (inputEnabled) {
::input_device = backend->openInputDevice("", 44100, 1, 16);
::input_enabled = true;
}
if (::input_device) { if (::input_device) {
::input_device->startCapture(); ::input_device->startCapture();
} }
@ -270,6 +276,10 @@ std::vector<std::string> audio::get_output_devices_names() {
void audio::set_input_device(const std::string& deviceName) { void audio::set_input_device(const std::string& deviceName) {
logger.info() << "setting input device to " << deviceName; logger.info() << "setting input device to " << deviceName;
if (!::input_enabled) {
logger.warning() << "unable to set input device due to project permissions";
return;
}
if (deviceName == audio::DEVICE_NONE) { if (deviceName == audio::DEVICE_NONE) {
if (::input_device) { if (::input_device) {
::input_device->stopCapture(); ::input_device->stopCapture();

View file

@ -402,7 +402,7 @@ namespace audio {
/// @brief Initialize audio system or use no audio mode /// @brief Initialize audio system or use no audio mode
/// @param enabled try to initialize actual audio /// @param enabled try to initialize actual audio
void initialize(bool enabled, AudioSettings& settings); void initialize(bool enabled, bool inputEnabled, AudioSettings& settings);
/// @brief Load audio file info and PCM data /// @brief Load audio file info and PCM data
/// @param file audio file /// @param file audio file

View file

@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "xml.hpp" #include "xml.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
@ -71,6 +72,42 @@ static void perform_rect(const xmlelement& root, model::Model& model) {
); );
} }
static void perform_triangle(const xmlelement& root, model::Model& model) {
auto pointA = root.attr("a").asVec3();
auto pointB = root.attr("b").asVec3();
auto pointC = root.attr("c").asVec3();
glm::vec2 uvs[3] {{0, 0}, {1, 0}, {1, 1}};
bool shading = true;
if (root.has("shading")) {
shading = to_boolean(root.attr("shading"));
}
glm::vec3 ba = pointB - pointA;
glm::vec3 ca = pointC - pointA;
glm::vec3 normal = glm::normalize(glm::cross(ba, ca));
if (root.has("uv")) {
root.attr("uv").asNumbers(glm::value_ptr(uvs[0]), 6);
} else {
UVRegion region {};
if (root.has("region")) {
region.set(root.attr("region").asVec4());
}
if (root.has("region-scale")) {
region.scale(root.attr("region-scale").asVec2());
}
for (auto& uv : uvs) {
uv = region.apply(uv);
}
}
std::string texture = root.attr("texture", "$0").getText();
auto& mesh = model.addMesh(texture, shading);
mesh.addTriangle(pointA, pointB, pointC, normal, uvs[0], uvs[1], uvs[2]);
}
static void perform_box(const xmlelement& root, model::Model& model) { static void perform_box(const xmlelement& root, model::Model& model) {
auto from = root.attr("from").asVec3(); auto from = root.attr("from").asVec3();
auto to = root.attr("to").asVec3(); auto to = root.attr("to").asVec3();
@ -181,6 +218,8 @@ static std::unique_ptr<model::Model> load_model(const xmlelement& root) {
perform_rect(*elem, model); perform_rect(*elem, model);
} else if (tag == "box") { } else if (tag == "box") {
perform_box(*elem, model); perform_box(*elem, model);
} else if (tag == "tri") {
perform_triangle(*elem, model);
} }
} }

View file

@ -105,6 +105,19 @@ glm::vec4 Attribute::asColor() const {
} }
} }
int Attribute::asNumbers(float* dst, size_t dstSize) const {
size_t pos = 0;
size_t count = 0;
while (pos < text.length() && count < dstSize) {
size_t end = text.find(',', pos);
dst[count++] = util::parse_double(
text, pos, std::min<size_t>(end - pos, text.length() - pos)
);
pos = end + 1;
}
return count;
}
Node::Node(std::string tag) : tag(std::move(tag)) { Node::Node(std::string tag) : tag(std::move(tag)) {
} }

View file

@ -29,6 +29,7 @@ namespace xml {
glm::vec3 asVec3() const; glm::vec3 asVec3() const;
glm::vec4 asVec4() const; glm::vec4 asVec4() const;
glm::vec4 asColor() const; glm::vec4 asColor() const;
int asNumbers(float* dst, size_t dstSize) const;
}; };
/// @brief XML element class. Text element has tag 'text' and attribute /// @brief XML element class. Text element has tag 'text' and attribute

View file

@ -34,11 +34,15 @@ std::unique_ptr<Content> ContentBuilder::build() {
if (def.variants) { if (def.variants) {
for (auto& variant : def.variants->variants) { for (auto& variant : def.variants->variants) {
variant.rt.solid = variant.model.type == BlockModelType::BLOCK; variant.rt.solid =
variant.model.type == BlockModelType::BLOCK ||
def.explictlySolid;
} }
def.defaults = def.variants->variants.at(0); def.defaults = def.variants->variants.at(0);
} else { } else {
def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; def.defaults.rt.solid =
def.defaults.model.type == BlockModelType::BLOCK ||
def.explictlySolid;
} }
const float EPSILON = 0.01f; const float EPSILON = 0.01f;

View file

@ -227,6 +227,7 @@ template<> void ContentUnitLoader<Block>::loadUnit(
root.at("tick-interval").get(def.tickInterval); root.at("tick-interval").get(def.tickInterval);
root.at("overlay-texture").get(def.overlayTexture); root.at("overlay-texture").get(def.overlayTexture);
root.at("translucent").get(def.translucent); root.at("translucent").get(def.translucent);
root.at("solid").get(def.explictlySolid);
if (root.has("fields")) { if (root.has("fields")) {
def.dataStruct = std::make_unique<StructLayout>(); def.dataStruct = std::make_unique<StructLayout>();

View file

@ -102,7 +102,7 @@ static network::Server& create_tcp_server(
} }
u64id_t serverId = network->openTcpServer( u64id_t serverId = network->openTcpServer(
port, port,
[&network, &dbgServer](u64id_t sid, u64id_t id) { [network, &dbgServer](u64id_t sid, u64id_t id) {
auto& connection = dynamic_cast<network::ReadableConnection&>( auto& connection = dynamic_cast<network::ReadableConnection&>(
*network->getConnection(id, true) *network->getConnection(id, true)
); );

View file

@ -13,8 +13,10 @@ namespace scripting {
} }
struct Permissions { struct Permissions {
static inline std::string WRITE_TO_USER = "write-to-user"; static inline std::string DEBUGGING = "debugging";
static inline std::string NETWORK = "network"; static inline std::string NETWORK = "network";
static inline std::string RECORD_AUDIO = "record-audio";
static inline std::string WRITE_TO_USER = "write-to-user";
std::set<std::string> permissions; std::set<std::string> permissions;

View file

@ -139,7 +139,8 @@ void Engine::initialize(CoreParameters coreParameters) {
editor = std::make_unique<devtools::Editor>(*this); editor = std::make_unique<devtools::Editor>(*this);
cmd = std::make_unique<cmd::CommandsInterpreter>(); cmd = std::make_unique<cmd::CommandsInterpreter>();
if (project->permissions.has(Permissions::NETWORK)) { if (project->permissions.has(Permissions::NETWORK) ||
!params.debugServerString.empty()) {
network = network::Network::create(settings.network); network = network::Network::create(settings.network);
} }
@ -160,7 +161,11 @@ void Engine::initialize(CoreParameters coreParameters) {
if (!params.headless) { if (!params.headless) {
initializeClient(); initializeClient();
} }
audio::initialize(!params.headless, settings.audio); audio::initialize(
!params.headless,
project->permissions.has(Permissions::RECORD_AUDIO),
settings.audio
);
if (settings.ui.language.get() == "auto") { if (settings.ui.language.get() == "auto") {
settings.ui.language.set( settings.ui.language.set(
@ -173,7 +178,7 @@ void Engine::initialize(CoreParameters coreParameters) {
scripting::initialize(this); scripting::initialize(this);
if (!isHeadless()) { if (!isHeadless()) {
gui->setPageLoader(scripting::create_page_loader()); gui->getMenu()->setPageLoader(scripting::create_page_loader());
} }
keepAlive(settings.ui.language.observe([this](auto lang) { keepAlive(settings.ui.language.observe([this](auto lang) {
langs::setup(lang, paths->resPaths.collectRoots()); langs::setup(lang, paths->resPaths.collectRoots());

View file

@ -41,6 +41,20 @@ void Mesh::addPlane(
); );
} }
void Mesh::addTriangle(
const glm::vec3& a,
const glm::vec3& b,
const glm::vec3& c,
const glm::vec3& norm,
const glm::vec2& uvA,
const glm::vec2& uvB,
const glm::vec2& uvC
) {
vertices.push_back({a, uvA, norm});
vertices.push_back({b, uvB, norm});
vertices.push_back({c, uvC, norm});
}
void Mesh::addRect( void Mesh::addRect(
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right, const glm::vec3& right,

View file

@ -39,6 +39,15 @@ namespace model {
const glm::vec3& up, const glm::vec3& up,
const glm::vec3& norm, const glm::vec3& norm,
const UVRegion& region const UVRegion& region
);
void addTriangle(
const glm::vec3& a,
const glm::vec3& b,
const glm::vec3& c,
const glm::vec3& norm,
const glm::vec2& uvA,
const glm::vec2& uvB,
const glm::vec2& uvC
); );
void addBox(const glm::vec3& pos, const glm::vec3& size); void addBox(const glm::vec3& pos, const glm::vec3& size);
void addBox( void addBox(

View file

@ -81,7 +81,7 @@ void BlocksRenderer::face(
const glm::vec4(&lights)[4], const glm::vec4(&lights)[4],
const glm::vec4& tint const glm::vec4& tint
) { ) {
if (vertexCount + 4 >= capacity) { if (vertexCount + 4 >= capacity || indexCount + 6 >= capacity) {
overflow = true; overflow = true;
return; return;
} }
@ -121,7 +121,7 @@ void BlocksRenderer::faceAO(
const UVRegion& region, const UVRegion& region,
bool lights bool lights
) { ) {
if (vertexCount + 4 >= capacity) { if (vertexCount + 4 >= capacity || indexCount + 6 >= capacity) {
overflow = true; overflow = true;
return; return;
} }
@ -160,7 +160,7 @@ void BlocksRenderer::face(
glm::vec4 tint, glm::vec4 tint,
bool lights bool lights
) { ) {
if (vertexCount + 4 >= capacity) { if (vertexCount + 4 >= capacity || indexCount + 6 >= capacity) {
overflow = true; overflow = true;
return; return;
} }
@ -275,19 +275,6 @@ void BlocksRenderer::blockAABB(
} }
} }
static bool is_aligned(const glm::vec3& v, float e = 1e-6f) {
if (std::abs(v.y) < e && std::abs(v.z) < e && std::abs(v.x) > e) {
return true;
}
if (std::abs(v.x) < e && std::abs(v.z) < e && std::abs(v.y) > e) {
return true;
}
if (std::abs(v.x) < e && std::abs(v.y) < e && std::abs(v.z) > e) {
return true;
}
return false;
}
void BlocksRenderer::blockCustomModel( void BlocksRenderer::blockCustomModel(
const glm::ivec3& icoord, const Block& block, blockstate states, bool lights, bool ao const glm::ivec3& icoord, const Block& block, blockstate states, bool lights, bool ao
) { ) {
@ -304,14 +291,34 @@ void BlocksRenderer::blockCustomModel(
Z = orient.axes[2]; Z = orient.axes[2];
} }
if (!block.rt.extended) {
glm::ivec3 offsets[6] {
{-1, 0, 0}, {0, -1, 0}, {0, 0, -1},
{1, 0, 0}, {0, 1, 0}, {0, 0, 1},
};
bool culled = true;
for (int i = 0; i < 6; i++) {
if (isOpen(icoord + offsets[i], block, variant)) {
culled = false;
break;
}
}
if (culled) {
return;
}
}
const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits)); const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits));
for (const auto& mesh : model.meshes) { for (const auto& mesh : model.meshes) {
if (vertexCount + mesh.vertices.size() >= capacity) { if (vertexCount + mesh.vertices.size() >= capacity
|| indexCount + mesh.vertices.size() >= capacity) {
overflow = true; overflow = true;
return; return;
} }
bool shading = mesh.shading && !block.shadeless; bool shading = mesh.shading && !block.shadeless;
for (int triangle = 0; triangle < mesh.vertices.size() / 3; triangle++) {
int trianglesCount = mesh.vertices.size() / 3;
for (int triangle = 0; triangle < trianglesCount; triangle++) {
auto r = mesh.vertices[triangle * 3 + (triangle % 2) * 2].coord - auto r = mesh.vertices[triangle * 3 + (triangle % 2) * 2].coord -
mesh.vertices[triangle * 3 + 1].coord; mesh.vertices[triangle * 3 + 1].coord;
r = r.x * X + r.y * Y + r.z * Z; r = r.x * X + r.y * Y + r.z * Z;
@ -328,8 +335,7 @@ void BlocksRenderer::blockCustomModel(
vp = vp.x * X + vp.y * Y + vp.z * Z; vp = vp.x * X + vp.y * Y + vp.z * Z;
if (!block.rt.extended if (!block.rt.extended
&& !isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant) && !isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant)) {
&& is_aligned(n)) {
continue; continue;
} }
@ -769,6 +775,10 @@ ChunkMesh BlocksRenderer::render(
const Chunk* chunk, const VoxelsRenderVolume& volume const Chunk* chunk, const VoxelsRenderVolume& volume
) { ) {
build(chunk, volume); build(chunk, volume);
assert(vertexCount <= capacity);
assert(indexCount <= capacity);
assert(denseIndexCount <= capacity);
return ChunkMesh{std::make_unique<Mesh<ChunkVertex>>( return ChunkMesh{std::make_unique<Mesh<ChunkVertex>>(
vertexBuffer.get(), vertexCount, vertexBuffer.get(), vertexCount,

View file

@ -53,6 +53,9 @@ Decorator::Decorator(
addParticles(def, pos); addParticles(def, pos);
} }
}); });
playerNamePreset.deserialize(engine.getResPaths().readCombinedObject(
"presets/text3d/player_name.toml"
));
for (const auto& [id, player] : *level.players) { for (const auto& [id, player] : *level.players) {
if (id == this->player.getId()) { if (id == this->player.getId()) {
continue; continue;
@ -63,9 +66,6 @@ Decorator::Decorator(
player->getPosition() player->getPosition()
)); ));
} }
playerNamePreset.deserialize(engine.getResPaths().readCombinedObject(
"presets/text3d/player_name.toml"
));
} }
void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { void Decorator::addParticles(const Block& def, const glm::ivec3& pos) {

View file

@ -67,15 +67,6 @@ GUI::GUI(Engine& engine)
GUI::~GUI() = default; GUI::~GUI() = default;
void GUI::setPageLoader(PageLoaderFunc pageLoader) {
this->pagesLoader = std::move(pageLoader);
menu->setPageLoader(this->pagesLoader);
}
PageLoaderFunc GUI::getPagesLoader() {
return pagesLoader;
}
std::shared_ptr<Menu> GUI::getMenu() { std::shared_ptr<Menu> GUI::getMenu() {
return menu; return menu;
} }

View file

@ -62,8 +62,6 @@ namespace gui {
class Container; class Container;
class Menu; class Menu;
using PageLoaderFunc = std::function<std::shared_ptr<UINode>(const std::string&)>;
/// @brief The main UI controller /// @brief The main UI controller
class GUI { class GUI {
Engine& engine; Engine& engine;
@ -83,8 +81,6 @@ namespace gui {
std::queue<runnable> postRunnables; std::queue<runnable> postRunnables;
std::vector<std::weak_ptr<UINode>> mouseOver; std::vector<std::weak_ptr<UINode>> mouseOver;
PageLoaderFunc pagesLoader;
float tooltipTimer = 0.0f; float tooltipTimer = 0.0f;
float doubleClickTimer = 0.0f; float doubleClickTimer = 0.0f;
float doubleClickDelay = 0.5f; float doubleClickDelay = 0.5f;
@ -101,10 +97,6 @@ namespace gui {
GUI(Engine& engine); GUI(Engine& engine);
~GUI(); ~GUI();
void setPageLoader(PageLoaderFunc pageLoader);
PageLoaderFunc getPagesLoader();
/// @brief Get the main menu (Menu) node /// @brief Get the main menu (Menu) node
std::shared_ptr<Menu> getMenu(); std::shared_ptr<Menu> getMenu();

View file

@ -1,60 +1,92 @@
#include "api_lua.hpp" #include "api_lua.hpp"
#include "io/io.hpp"
#include "io/devices/MemoryDevice.hpp"
#include "logic/scripting/scripting.hpp"
#include "content/ContentControl.hpp" #include "content/ContentControl.hpp"
#include "devtools/Project.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp" #include "engine/EnginePaths.hpp"
#include "frontend/locale.hpp"
#include "graphics/ui/elements/Menu.hpp"
#include "graphics/ui/gui_util.hpp"
#include "graphics/ui/GUI.hpp"
#include "io/devices/MemoryDevice.hpp"
#include "io/io.hpp"
#include "io/settings_io.hpp"
#include "logic/EngineController.hpp"
#include "logic/LevelController.hpp"
#include "logic/scripting/scripting.hpp"
#include "network/Network.hpp" #include "network/Network.hpp"
#include "util/platform.hpp" #include "util/platform.hpp"
#include "util/stringutil.hpp"
#include "window/Window.hpp" #include "window/Window.hpp"
#include "world/Level.hpp"
using namespace scripting; using namespace scripting;
static int l_start_debug_instance(lua::State* L) { /// @brief Check if content is loaded
int port = lua::tointeger(L, 1); static int l_is_content_loaded(lua::State* L) {
if (port == 0) { return lua::pushboolean(L, content != nullptr);
auto network = engine->getNetwork();
if (network == nullptr) {
throw std::runtime_error("project has no network permission");
}
port = network->findFreePort();
if (port == -1) {
throw std::runtime_error("could not find free port");
}
}
const auto& paths = engine->getPaths();
std::vector<std::string> args {
"--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(),
"--dbg-server", "tcp:" + std::to_string(port),
};
platform::new_engine_instance(std::move(args));
return lua::pushinteger(L, port);
} }
static int l_focus(lua::State* L) { /// @brief Load content
engine->getWindow().focus(); static int l_load_content(lua::State* L) {
content_control->loadContent();
return 0; return 0;
} }
static int l_create_memory_device(lua::State* L) { /// @brief Reset content, excluding specified packs modules
std::string name = lua::require_string(L, 1); static int l_reset_content(lua::State* L) {
if (io::get_device(name)) { if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
std::vector<std::string> nonResetPacks;
if (lua::istable(L, 1)) {
int len = lua::objlen(L, 1);
for (int i = 0; i < len; i++) {
lua::rawgeti(L, i + 1, 1);
nonResetPacks.emplace_back(lua::require_lstring(L, -1));
lua::pop(L);
}
}
content_control->resetContent(std::move(nonResetPacks));
return 0;
}
/// @brief Reconfigure packs
/// @param addPacks An array of packs to add
/// @param remPacks An array of packs to remove
static int l_reconfig_packs(lua::State* L) {
if (!lua::istable(L, 1)) {
throw std::runtime_error("strings array expected as the first argument");
}
if (!lua::istable(L, 2)) {
throw std::runtime_error("strings array expected as the second argument");
}
std::vector<std::string> addPacks;
int addLen = lua::objlen(L, 1);
for (int i = 0; i < addLen; i++) {
lua::rawgeti(L, i + 1, 1);
addPacks.emplace_back(lua::require_lstring(L, -1));
lua::pop(L);
}
std::vector<std::string> remPacks;
int remLen = lua::objlen(L, 2);
for (int i = 0; i < remLen; i++) {
lua::rawgeti(L, i + 1, 2);
remPacks.emplace_back(lua::require_lstring(L, -1));
lua::pop(L);
}
auto engineController = engine->getController();
try {
engineController->reconfigPacks(controller, addPacks, remPacks);
} catch (const contentpack_error& err) {
throw std::runtime_error( throw std::runtime_error(
"entry-point '" + name + "' is already used" std::string(err.what()) + " [" + err.getPackId() + " ]"
); );
} }
if (name.find(':') != std::string::npos) {
throw std::runtime_error("invalid entry point name");
}
io::set_device(name, std::make_unique<io::MemoryDevice>());
return 0; return 0;
} }
/// @brief Get content sources list
static int l_get_content_sources(lua::State* L) { static int l_get_content_sources(lua::State* L) {
const auto& sources = engine->getContentControl().getContentSources(); const auto& sources = engine->getContentControl().getContentSources();
lua::createtable(L, static_cast<int>(sources.size()), 0); lua::createtable(L, static_cast<int>(sources.size()), 0);
@ -65,6 +97,7 @@ static int l_get_content_sources(lua::State* L) {
return 1; return 1;
} }
/// @brief Set content sources list
static int l_set_content_sources(lua::State* L) { static int l_set_content_sources(lua::State* L) {
if (!lua::istable(L, 1)) { if (!lua::istable(L, 1)) {
throw std::runtime_error("table expected as argument 1"); throw std::runtime_error("table expected as argument 1");
@ -80,18 +113,290 @@ static int l_set_content_sources(lua::State* L) {
return 0; return 0;
} }
/// @brief Reset content sources to defaults
static int l_reset_content_sources(lua::State* L) { static int l_reset_content_sources(lua::State* L) {
engine->getContentControl().resetContentSources(); engine->getContentControl().resetContentSources();
return 0; return 0;
} }
/// @brief Get a setting value
/// @param name The name of the setting
/// @return The value of the setting
static int l_get_setting(lua::State* L) {
auto name = lua::require_string(L, 1);
const auto value = engine->getSettingsHandler().getValue(name);
return lua::pushvalue(L, value);
}
/// @brief Set a setting value
/// @param name The name of the setting
/// @param value The new value for the setting
static int l_set_setting(lua::State* L) {
auto name = lua::require_string(L, 1);
const auto value = lua::tovalue(L, 2);
engine->getSettingsHandler().setValue(name, value);
return 0;
}
/// @brief Convert a setting value to a string
/// @param name The name of the setting
/// @return The string representation of the setting value
static int l_str_setting(lua::State* L) {
auto name = lua::require_string(L, 1);
const auto string = engine->getSettingsHandler().toString(name);
return lua::pushstring(L, string);
}
/// @brief Get information about a setting
/// @param name The name of the setting
/// @return A table with information about the setting
static int l_get_setting_info(lua::State* L) {
auto name = lua::require_string(L, 1);
auto setting = engine->getSettingsHandler().getSetting(name);
lua::createtable(L, 0, 1);
if (auto number = dynamic_cast<NumberSetting*>(setting)) {
lua::pushnumber(L, number->getMin());
lua::setfield(L, "min");
lua::pushnumber(L, number->getMax());
lua::setfield(L, "max");
lua::pushnumber(L, number->getDefault());
lua::setfield(L, "def");
return 1;
}
if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
lua::pushinteger(L, integer->getMin());
lua::setfield(L, "min");
lua::pushinteger(L, integer->getMax());
lua::setfield(L, "max");
lua::pushinteger(L, integer->getDefault());
lua::setfield(L, "def");
return 1;
}
if (auto boolean = dynamic_cast<FlagSetting*>(setting)) {
lua::pushboolean(L, boolean->getDefault());
lua::setfield(L, "def");
return 1;
}
if (auto string = dynamic_cast<StringSetting*>(setting)) {
lua::pushstring(L, string->getDefault());
lua::setfield(L, "def");
return 1;
}
lua::pop(L);
throw std::runtime_error("unsupported setting type");
}
/// @brief Open folder in file explorer
static int l_open_folder(lua::State* L) {
platform::open_folder(io::resolve(lua::require_string(L, 1)));
return 0;
}
/// @brief Open URL in default browser with confirmation dialog
static int l_open_url(lua::State* L) {
auto url = lua::require_string(L, 1);
std::wstring msg = langs::get(L"Are you sure you want to open the link:") +
L"\n" + util::str2wstr_utf8(url) +
std::wstring(L"?");
auto menu = engine->getGUI().getMenu();
guiutil::confirm(*engine, msg, [url, menu]() {
platform::open_url(url);
if (!menu->back()) {
menu->reset();
}
});
return 0;
}
/// @brief Bring application window to focus
static int l_focus(lua::State* L) {
engine->getWindow().focus();
return 0;
}
/// @brief Set window title
static int l_set_title(lua::State* L) {
auto title = lua::require_string(L, 1);
engine->getWindow().setTitle(title);
return 0;
}
/// @brief Quit the game
static int l_quit(lua::State*) {
engine->quit();
return 0;
}
/// @brief Creating new world
/// @param name Name world
/// @param seed Seed world
/// @param generator Type of generation
static int l_new_world(lua::State* L) {
auto name = lua::require_string(L, 1);
auto seed = lua::require_string(L, 2);
auto generator = lua::require_string(L, 3);
int64_t localPlayer = 0;
if (lua::gettop(L) >= 4) {
localPlayer = lua::tointeger(L, 4);
}
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
auto controller = engine->getController();
controller->setLocalPlayer(localPlayer);
controller->createWorld(name, seed, generator);
return 0;
}
/// @brief Open world
/// @param name Name world
static int l_open_world(lua::State* L) {
auto name = lua::require_string(L, 1);
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
auto controller = engine->getController();
controller->setLocalPlayer(0);
controller->openWorld(name, false);
return 0;
}
/// @brief Reopen world
static int l_reopen_world(lua::State*) {
auto controller = engine->getController();
if (level == nullptr) {
throw std::runtime_error("no world open");
}
controller->reopenWorld(level->getWorld());
return 0;
}
/// @brief Save world
static int l_save_world(lua::State* L) {
if (controller == nullptr) {
throw std::runtime_error("no world open");
}
controller->saveWorld();
return 0;
}
/// @brief Close world
/// @param flag Save world (bool)
static int l_close_world(lua::State* L) {
if (controller == nullptr) {
throw std::runtime_error("no world open");
}
controller->processBeforeQuit();
bool save_world = lua::toboolean(L, 1);
if (save_world) {
controller->saveWorld();
}
engine->onWorldClosed();
return 0;
}
/// @brief Delete world
/// @param name Name world
static int l_delete_world(lua::State* L) {
auto name = lua::require_string(L, 1);
auto controller = engine->getController();
controller->deleteWorld(name);
return 0;
}
/// @brief Get engine version
/// @return major, minor
static int l_get_version(lua::State* L) {
return lua::pushvec_stack(
L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR)
);
}
/// @brief Create in-memory named IO device
static int l_create_memory_device(lua::State* L) {
std::string name = lua::require_string(L, 1);
if (io::get_device(name)) {
throw std::runtime_error(
"entry-point '" + name + "' is already used"
);
}
if (name.find(':') != std::string::npos) {
throw std::runtime_error("invalid entry point name");
}
io::set_device(name, std::make_unique<io::MemoryDevice>());
return 0;
}
/// @brief Start a new engine instance with debugging server
static int l_start_debug_instance(lua::State* L) {
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
throw std::runtime_error("project has no debugging permission");
}
int port = lua::tointeger(L, 1);
if (port == 0) {
auto network = engine->getNetwork();
if (network == nullptr) {
throw std::runtime_error("project has no network permission");
}
port = network->findFreePort();
if (port == -1) {
throw std::runtime_error("could not find free port");
}
}
auto projectPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : "";
const auto& paths = engine->getPaths();
std::vector<std::string> args {
"--res", paths.getResourcesFolder().u8string(),
"--dir", paths.getUserFilesFolder().u8string(),
"--dbg-server", "tcp:" + std::to_string(port),
};
if (!projectPath.empty()) {
args.emplace_back("--project");
args.emplace_back(io::resolve(std::string(projectPath)).string());
}
platform::new_engine_instance(std::move(args));
return lua::pushinteger(L, port);
}
const luaL_Reg applib[] = { const luaL_Reg applib[] = {
{"start_debug_instance", lua::wrap<l_start_debug_instance>}, /// content
{"focus", lua::wrap<l_focus>}, {"is_content_loaded", lua::wrap<l_is_content_loaded>},
{"create_memory_device", lua::wrap<l_create_memory_device>}, {"load_content", lua::wrap<l_load_content>},
{"reset_content", lua::wrap<l_reset_content>},
{"reconfig_packs", lua::wrap<l_reconfig_packs>},
{"get_content_sources", lua::wrap<l_get_content_sources>}, {"get_content_sources", lua::wrap<l_get_content_sources>},
{"set_content_sources", lua::wrap<l_set_content_sources>}, {"set_content_sources", lua::wrap<l_set_content_sources>},
{"reset_content_sources", lua::wrap<l_reset_content_sources>}, {"reset_content_sources", lua::wrap<l_reset_content_sources>},
// for other functions see libcore.cpp and stdlib.lua /// settings
{"get_setting", lua::wrap<l_get_setting>},
{"set_setting", lua::wrap<l_set_setting>},
{"str_setting", lua::wrap<l_str_setting>},
{"get_setting_info", lua::wrap<l_get_setting_info>},
/// system applications
{"open_folder", lua::wrap<l_open_folder>},
{"open_url", lua::wrap<l_open_url>},
/// window
{"focus", lua::wrap<l_focus>},
{"set_title", lua::wrap<l_set_title>},
{"quit", lua::wrap<l_quit>},
/// world
{"new_world", lua::wrap<l_new_world>},
{"open_world", lua::wrap<l_open_world>},
{"reopen_world", lua::wrap<l_reopen_world>},
{"save_world", lua::wrap<l_save_world>},
{"close_world", lua::wrap<l_close_world>},
{"delete_world", lua::wrap<l_delete_world>},
/// other
{"get_version", lua::wrap<l_get_version>},
{"create_memory_device", lua::wrap<l_create_memory_device>},
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -476,8 +476,10 @@ static int l_raycast(lua::State* L) {
auto start = lua::tovec<3>(L, 1); auto start = lua::tovec<3>(L, 1);
auto dir = lua::tovec<3>(L, 2); auto dir = lua::tovec<3>(L, 2);
auto maxDistance = lua::tonumber(L, 3); auto maxDistance = lua::tonumber(L, 3);
bool includeNonSelectable = false;
std::set<blockid_t> filteredBlocks {}; std::set<blockid_t> filteredBlocks {};
if (lua::gettop(L) >= 5) { const int luaStackSize = lua::gettop(L);
if (luaStackSize >= 5) {
if (lua::istable(L, 5)) { if (lua::istable(L, 5)) {
int addLen = lua::objlen(L, 5); int addLen = lua::objlen(L, 5);
for (int i = 0; i < addLen; i++) { for (int i = 0; i < addLen; i++) {
@ -493,6 +495,9 @@ static int l_raycast(lua::State* L) {
throw std::runtime_error("table expected for filter"); throw std::runtime_error("table expected for filter");
} }
} }
if (luaStackSize >= 6) {
includeNonSelectable = lua::toboolean(L, 6);
}
glm::vec3 end; glm::vec3 end;
glm::ivec3 normal; glm::ivec3 normal;
glm::ivec3 iend; glm::ivec3 iend;
@ -504,9 +509,10 @@ static int l_raycast(lua::State* L) {
end, end,
normal, normal,
iend, iend,
filteredBlocks filteredBlocks,
includeNonSelectable
)) { )) {
if (lua::gettop(L) >= 4 && !lua::isnil(L, 4)) { if (luaStackSize >= 4 && !lua::isnil(L, 4)) {
lua::pushvalue(L, 4); lua::pushvalue(L, 4);
} else { } else {
lua::createtable(L, 0, 5); lua::createtable(L, 0, 5);

View file

@ -1,274 +1,9 @@
#include <memory> #include "api_lua.hpp"
#include <vector>
#include <sstream> #include <sstream>
#include "api_lua.hpp"
#include "constants.hpp"
#include "assets/Assets.hpp"
#include "content/Content.hpp"
#include "content/ContentControl.hpp"
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "io/io.hpp"
#include "io/settings_io.hpp"
#include "frontend/menu.hpp"
#include "frontend/screens/MenuScreen.hpp"
#include "graphics/core/Texture.hpp"
#include "logic/EngineController.hpp"
#include "logic/LevelController.hpp"
#include "util/listutil.hpp"
#include "util/platform.hpp"
#include "world/Level.hpp"
#include "world/generator/WorldGenerator.hpp"
#include "util/platform.hpp"
#include "frontend/locale.hpp"
#include "graphics/ui/gui_util.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/ui/elements/Menu.hpp"
#include "window/Window.hpp"
using namespace scripting; using namespace scripting;
static int l_get_version(lua::State* L) {
return lua::pushvec_stack(
L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR)
);
}
static int l_load_content(lua::State* L) {
content_control->loadContent();
return 0;
}
static int l_reset_content(lua::State* L) {
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
std::vector<std::string> nonResetPacks;
if (lua::istable(L, 1)) {
int len = lua::objlen(L, 1);
for (int i = 0; i < len; i++) {
lua::rawgeti(L, i + 1, 1);
nonResetPacks.emplace_back(lua::require_lstring(L, -1));
lua::pop(L);
}
}
content_control->resetContent(std::move(nonResetPacks));
return 0;
}
static int l_is_content_loaded(lua::State* L) {
return lua::pushboolean(L, content != nullptr);
}
/// @brief Creating new world
/// @param name Name world
/// @param seed Seed world
/// @param generator Type of generation
static int l_new_world(lua::State* L) {
auto name = lua::require_string(L, 1);
auto seed = lua::require_string(L, 2);
auto generator = lua::require_string(L, 3);
int64_t localPlayer = 0;
if (lua::gettop(L) >= 4) {
localPlayer = lua::tointeger(L, 4);
}
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
auto controller = engine->getController();
controller->setLocalPlayer(localPlayer);
controller->createWorld(name, seed, generator);
return 0;
}
/// @brief Open world
/// @param name Name world
static int l_open_world(lua::State* L) {
auto name = lua::require_string(L, 1);
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
auto controller = engine->getController();
controller->setLocalPlayer(0);
controller->openWorld(name, false);
return 0;
}
/// @brief Reopen world
static int l_reopen_world(lua::State*) {
auto controller = engine->getController();
if (level == nullptr) {
throw std::runtime_error("no world open");
}
controller->reopenWorld(level->getWorld());
return 0;
}
/// @brief Save world
static int l_save_world(lua::State* L) {
if (controller == nullptr) {
throw std::runtime_error("no world open");
}
controller->saveWorld();
return 0;
}
/// @brief Close world
/// @param flag Save world (bool)
static int l_close_world(lua::State* L) {
if (controller == nullptr) {
throw std::runtime_error("no world open");
}
controller->processBeforeQuit();
bool save_world = lua::toboolean(L, 1);
if (save_world) {
controller->saveWorld();
}
engine->onWorldClosed();
return 0;
}
/// @brief Delete world
/// @param name Name world
static int l_delete_world(lua::State* L) {
auto name = lua::require_string(L, 1);
auto controller = engine->getController();
controller->deleteWorld(name);
return 0;
}
/// @brief Reconfigure packs
/// @param addPacks An array of packs to add
/// @param remPacks An array of packs to remove
static int l_reconfig_packs(lua::State* L) {
if (!lua::istable(L, 1)) {
throw std::runtime_error("strings array expected as the first argument");
}
if (!lua::istable(L, 2)) {
throw std::runtime_error("strings array expected as the second argument");
}
std::vector<std::string> addPacks;
int addLen = lua::objlen(L, 1);
for (int i = 0; i < addLen; i++) {
lua::rawgeti(L, i + 1, 1);
addPacks.emplace_back(lua::require_lstring(L, -1));
lua::pop(L);
}
std::vector<std::string> remPacks;
int remLen = lua::objlen(L, 2);
for (int i = 0; i < remLen; i++) {
lua::rawgeti(L, i + 1, 2);
remPacks.emplace_back(lua::require_lstring(L, -1));
lua::pop(L);
}
auto engineController = engine->getController();
try {
engineController->reconfigPacks(controller, addPacks, remPacks);
} catch (const contentpack_error& err) {
throw std::runtime_error(
std::string(err.what()) + " [" + err.getPackId() + " ]"
);
}
return 0;
}
/// @brief Get a setting value
/// @param name The name of the setting
/// @return The value of the setting
static int l_get_setting(lua::State* L) {
auto name = lua::require_string(L, 1);
const auto value = engine->getSettingsHandler().getValue(name);
return lua::pushvalue(L, value);
}
/// @brief Set a setting value
/// @param name The name of the setting
/// @param value The new value for the setting
static int l_set_setting(lua::State* L) {
auto name = lua::require_string(L, 1);
const auto value = lua::tovalue(L, 2);
engine->getSettingsHandler().setValue(name, value);
return 0;
}
/// @brief Convert a setting value to a string
/// @param name The name of the setting
/// @return The string representation of the setting value
static int l_str_setting(lua::State* L) {
auto name = lua::require_string(L, 1);
const auto string = engine->getSettingsHandler().toString(name);
return lua::pushstring(L, string);
}
/// @brief Get information about a setting
/// @param name The name of the setting
/// @return A table with information about the setting
static int l_get_setting_info(lua::State* L) {
auto name = lua::require_string(L, 1);
auto setting = engine->getSettingsHandler().getSetting(name);
lua::createtable(L, 0, 1);
if (auto number = dynamic_cast<NumberSetting*>(setting)) {
lua::pushnumber(L, number->getMin());
lua::setfield(L, "min");
lua::pushnumber(L, number->getMax());
lua::setfield(L, "max");
lua::pushnumber(L, number->getDefault());
lua::setfield(L, "def");
return 1;
}
if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
lua::pushinteger(L, integer->getMin());
lua::setfield(L, "min");
lua::pushinteger(L, integer->getMax());
lua::setfield(L, "max");
lua::pushinteger(L, integer->getDefault());
lua::setfield(L, "def");
return 1;
}
if (auto boolean = dynamic_cast<FlagSetting*>(setting)) {
lua::pushboolean(L, boolean->getDefault());
lua::setfield(L, "def");
return 1;
}
if (auto string = dynamic_cast<StringSetting*>(setting)) {
lua::pushstring(L, string->getDefault());
lua::setfield(L, "def");
return 1;
}
lua::pop(L);
throw std::runtime_error("unsupported setting type");
}
static int l_open_folder(lua::State* L) {
platform::open_folder(io::resolve(lua::require_string(L, 1)));
return 0;
}
static int l_open_url(lua::State* L) {
auto url = lua::require_string(L, 1);
std::wstring msg = langs::get(L"Are you sure you want to open the link:") +
L"\n" + util::str2wstr_utf8(url) +
std::wstring(L"?");
auto menu = engine->getGUI().getMenu();
guiutil::confirm(*engine, msg, [url, menu]() {
platform::open_url(url);
if (!menu->back()) {
menu->reset();
}
});
return 0;
}
/// @brief Quit the game
static int l_quit(lua::State*) {
engine->quit();
return 0;
}
static int l_blank(lua::State*) { static int l_blank(lua::State*) {
return 0; return 0;
} }
@ -300,33 +35,8 @@ static int l_capture_output(lua::State* L) {
return 1; return 1;
} }
static int l_set_title(lua::State* L) {
auto title = lua::require_string(L, 1);
engine->getWindow().setTitle(title);
return 0;
}
const luaL_Reg corelib[] = { const luaL_Reg corelib[] = {
{"blank", lua::wrap<l_blank>}, {"blank", lua::wrap<l_blank>},
{"get_version", lua::wrap<l_get_version>},
{"load_content", lua::wrap<l_load_content>},
{"reset_content", lua::wrap<l_reset_content>},
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
{"new_world", lua::wrap<l_new_world>},
{"open_world", lua::wrap<l_open_world>},
{"reopen_world", lua::wrap<l_reopen_world>},
{"save_world", lua::wrap<l_save_world>},
{"close_world", lua::wrap<l_close_world>},
{"delete_world", lua::wrap<l_delete_world>},
{"reconfig_packs", lua::wrap<l_reconfig_packs>},
{"get_setting", lua::wrap<l_get_setting>},
{"set_setting", lua::wrap<l_set_setting>},
{"str_setting", lua::wrap<l_str_setting>},
{"get_setting_info", lua::wrap<l_get_setting_info>},
{"open_folder", lua::wrap<l_open_folder>},
{"open_url", lua::wrap<l_open_url>},
{"quit", lua::wrap<l_quit>},
{"capture_output", lua::wrap<l_capture_output>}, {"capture_output", lua::wrap<l_capture_output>},
{"set_title", lua::wrap<l_set_title>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -141,8 +141,10 @@ static int l_raycast(lua::State* L) {
auto dir = lua::tovec<3>(L, 2); auto dir = lua::tovec<3>(L, 2);
auto maxDistance = lua::tonumber(L, 3); auto maxDistance = lua::tonumber(L, 3);
auto ignoreEntityId = lua::tointeger(L, 4); auto ignoreEntityId = lua::tointeger(L, 4);
bool includeNonSelectable = false;
std::set<blockid_t> filteredBlocks {}; std::set<blockid_t> filteredBlocks {};
if (lua::gettop(L) >= 6) { const int luaStackSize = lua::gettop(L);
if (luaStackSize >= 6) {
if (lua::istable(L, 6)) { if (lua::istable(L, 6)) {
int addLen = lua::objlen(L, 6); int addLen = lua::objlen(L, 6);
for (int i = 0; i < addLen; i++) { for (int i = 0; i < addLen; i++) {
@ -158,6 +160,9 @@ static int l_raycast(lua::State* L) {
throw std::runtime_error("table expected for filter"); throw std::runtime_error("table expected for filter");
} }
} }
if (luaStackSize >= 7) {
includeNonSelectable = lua::toboolean(L, 6);
}
glm::vec3 end; glm::vec3 end;
glm::ivec3 normal; glm::ivec3 normal;
@ -173,14 +178,15 @@ static int l_raycast(lua::State* L) {
end, end,
normal, normal,
iend, iend,
filteredBlocks filteredBlocks,
includeNonSelectable
)) { )) {
maxDistance = glm::distance(start, end); maxDistance = glm::distance(start, end);
block = voxel->id; block = voxel->id;
} }
if (auto ray = if (auto ray =
level->entities->rayCast(start, dir, maxDistance, ignoreEntityId)) { level->entities->rayCast(start, dir, maxDistance, ignoreEntityId)) {
if (lua::gettop(L) >= 5 && !lua::isnil(L, 5)) { if (luaStackSize >= 5 && !lua::isnil(L, 5)) {
lua::pushvalue(L, 5); lua::pushvalue(L, 5);
} else { } else {
lua::createtable(L, 0, 6); lua::createtable(L, 0, 6);

View file

@ -1111,6 +1111,16 @@ static int l_gui_load_document(lua::State* L) {
); );
auto document = documentPtr.get(); auto document = documentPtr.get();
engine->getAssets()->store(std::move(documentPtr), alias); engine->getAssets()->store(std::move(documentPtr), alias);
// namespace extension
if (lua::istable(L, 4)) {
if (lua::get_from(L, "table", "merge")) {
lua::pushenv(L, *document->getEnvironment());
lua::pushvalue(L, 4);
lua::call(L, 2, 0);
lua::pop(L);
}
}
scripting::on_ui_open(document, {args}); scripting::on_ui_open(document, {args});
return 0; return 0;
} }

View file

@ -2,6 +2,7 @@
#include "coders/json.hpp" #include "coders/json.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "network/Network.hpp" #include "network/Network.hpp"
#include "devtools/Project.hpp"
#include <variant> #include <variant>
#include <utility> #include <utility>
@ -439,7 +440,7 @@ static int l_is_nodelay(lua::State* L, network::Network& network) {
return lua::pushboolean(L, false); return lua::pushboolean(L, false);
} }
static int l_pull_events(lua::State* L, network::Network& network) { static int l_pull_events(lua::State* L) {
std::vector<NetworkEvent> local_queue; std::vector<NetworkEvent> local_queue;
{ {
std::lock_guard lock(events_queue_mutex); std::lock_guard lock(events_queue_mutex);
@ -528,7 +529,8 @@ int wrap(lua_State* L) {
int result = 0; int result = 0;
try { try {
auto network = engine->getNetwork(); auto network = engine->getNetwork();
if (network == nullptr) { const auto& permissions = engine->getProject().permissions;
if (network == nullptr || !permissions.has(Permissions::NETWORK)) {
throw std::runtime_error( throw std::runtime_error(
"network subsystem is not available in the project" "network subsystem is not available in the project"
); );
@ -554,7 +556,7 @@ const luaL_Reg networklib[] = {
{"get_total_download", wrap<l_get_total_download>}, {"get_total_download", wrap<l_get_total_download>},
{"find_free_port", wrap<l_find_free_port>}, {"find_free_port", wrap<l_find_free_port>},
{"is_available", lua::wrap<l_is_available>}, {"is_available", lua::wrap<l_is_available>},
{"__pull_events", wrap<l_pull_events>}, {"__pull_events", lua::wrap<l_pull_events>},
{"__open_tcp", wrap<l_open_tcp>}, {"__open_tcp", wrap<l_open_tcp>},
{"__open_udp", wrap<l_open_udp>}, {"__open_udp", wrap<l_open_udp>},
{"__closeserver", wrap<l_closeserver>}, {"__closeserver", wrap<l_closeserver>},

View file

@ -324,8 +324,10 @@ static int l_is_suspended(lua::State* L) {
} }
static int l_set_suspended(lua::State* L) { static int l_set_suspended(lua::State* L) {
if (auto player = get_player(L, 1)) { if (lua::toboolean(L, 2)) {
player->setSuspended(lua::toboolean(L, 2)); level->players->suspend(lua::tointeger(L, 1));
} else {
level->players->resume(lua::tointeger(L, 1));
} }
return 0; return 0;
} }

View file

@ -34,7 +34,9 @@ static int l_get_intensity(lua::State* L) {
static int l_set_intensity(lua::State* L) { static int l_set_intensity(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1)); size_t index = static_cast<size_t>(lua::tointeger(L, 1));
float value = lua::tonumber(L, 2); float value = lua::tonumber(L, 2);
post_processing->getEffect(index)->setIntensity(value); if (auto effect = post_processing->getEffect(index)) {
effect->setIntensity(value);
}
return 0; return 0;
} }

View file

@ -66,6 +66,8 @@ static void create_libs(State* L, StateType stateType) {
if (stateType == StateType::SCRIPT) { if (stateType == StateType::SCRIPT) {
openlib(L, "app", applib); openlib(L, "app", applib);
lua::getglobal(L, "app");
lua::setglobal(L, "__vc_app");
} else if (stateType == StateType::BASE) { } else if (stateType == StateType::BASE) {
openlib(L, "__vc_app", applib); openlib(L, "__vc_app", applib);
} }

View file

@ -105,10 +105,8 @@ void Player::postUpdate() {
if (flight && hitbox.grounded && !noclip) { if (flight && hitbox.grounded && !noclip) {
flight = false; flight = false;
} }
if (spawnpoint.y <= 0.1) { for (int i = 0; i < SPAWN_ATTEMPTS_PER_UPDATE && std::isnan(spawnpoint.x); i++) {
for (int i = 0; i < SPAWN_ATTEMPTS_PER_UPDATE; i++) { attemptToFindSpawnpoint();
attemptToFindSpawnpoint();
}
} }
} }

View file

@ -49,7 +49,7 @@ class Player : public Serializable {
int chosenSlot; int chosenSlot;
glm::vec3 position; glm::vec3 position;
glm::vec3 spawnpoint {}; glm::vec3 spawnpoint {std::nanf(""), std::nanf(""), std::nanf("")};
std::shared_ptr<Inventory> inventory; std::shared_ptr<Inventory> inventory;
bool suspended = false; bool suspended = false;
bool flight = false; bool flight = false;

View file

@ -99,7 +99,7 @@ void Players::suspend(int64_t id) {
} }
player->setSuspended(true); player->setSuspended(true);
level.entities->despawn(player->getEntity()); level.entities->despawn(player->getEntity());
player->setEntity(0); player->setEntity(ENTITY_AUTO);
} }
} }

View file

@ -281,6 +281,7 @@ void platform::new_engine_instance(const std::vector<std::string>& args) {
ss << " >/dev/null &"; ss << " >/dev/null &";
auto command = ss.str(); auto command = ss.str();
logger.info() << command;
if (int res = system(command.c_str())) { if (int res = system(command.c_str())) {
throw std::runtime_error( throw std::runtime_error(
"starting an engine instance failed with code: " + "starting an engine instance failed with code: " +

View file

@ -125,9 +125,6 @@ Block::Block(std::string name, const std::string& texture)
void Block::cloneTo(Block& dst) { void Block::cloneTo(Block& dst) {
dst.caption = caption; dst.caption = caption;
for (int i = 0; i < 6; i++) {
dst.defaults = defaults;
}
dst.defaults = defaults; dst.defaults = defaults;
if (variants) { if (variants) {
dst.variants = std::make_unique<Variants>(*variants); dst.variants = std::make_unique<Variants>(*variants);
@ -155,6 +152,7 @@ void Block::cloneTo(Block& dst) {
dst.tickInterval = tickInterval; dst.tickInterval = tickInterval;
dst.overlayTexture = overlayTexture; dst.overlayTexture = overlayTexture;
dst.translucent = translucent; dst.translucent = translucent;
dst.explictlySolid = explictlySolid;
dst.tags = tags; dst.tags = tags;
if (particles) { if (particles) {
dst.particles = std::make_unique<ParticlesPreset>(*particles); dst.particles = std::make_unique<ParticlesPreset>(*particles);

View file

@ -230,6 +230,9 @@ public:
/// @brief Block has semi-transparent texture /// @brief Block has semi-transparent texture
bool translucent = false; bool translucent = false;
/// @brief Explicitly overriding 'solid' property if true assigned
bool explictlySolid = false;
/// @brief Set of block physical hitboxes /// @brief Set of block physical hitboxes
std::vector<AABB> hitboxes {AABB()}; std::vector<AABB> hitboxes {AABB()};

View file

@ -215,10 +215,11 @@ voxel* Chunks::rayCast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter std::set<blockid_t> filter,
bool includeNonSelectable
) const { ) const {
return blocks_agent::raycast( return blocks_agent::raycast(
*this, start, dir, maxDist, end, norm, iend, std::move(filter) *this, start, dir, maxDist, end, norm, iend, std::move(filter), includeNonSelectable
); );
} }

View file

@ -113,7 +113,8 @@ public:
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter = {} std::set<blockid_t> filter = {},
bool includeNonSelectable = false
) const; ) const;
glm::vec3 rayCastToObstacle( glm::vec3 rayCastToObstacle(

View file

@ -221,7 +221,8 @@ static inline voxel* raycast_blocks(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter std::set<blockid_t> filter,
bool includeNonSelectable
) { ) {
const auto& blocks = chunks.getContentIndices().blocks; const auto& blocks = chunks.getContentIndices().blocks;
float px = start.x; float px = start.x;
@ -264,8 +265,8 @@ static inline voxel* raycast_blocks(
} }
const auto& def = blocks.require(voxel->id); const auto& def = blocks.require(voxel->id);
if ((filter.empty() && def.selectable) || if (voxel->id != BLOCK_AIR && (def.selectable || includeNonSelectable) &&
(!filter.empty() && filter.find(def.rt.id) == filter.end())) { (filter.empty() || filter.find(def.rt.id) == filter.end())) {
end.x = px + t * dx; end.x = px + t * dx;
end.y = py + t * dy; end.y = py + t * dy;
end.z = pz + t * dz; end.z = pz + t * dz;
@ -362,9 +363,10 @@ voxel* blocks_agent::raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter std::set<blockid_t> filter,
bool includeNonSelectable
) { ) {
return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter); return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable);
} }
voxel* blocks_agent::raycast( voxel* blocks_agent::raycast(
@ -375,9 +377,10 @@ voxel* blocks_agent::raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter std::set<blockid_t> filter,
bool includeNonSelectable
) { ) {
return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter); return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable);
} }
// reduce nesting on next modification // reduce nesting on next modification

View file

@ -419,6 +419,7 @@ inline void set_rotation(
/// @param norm [out] surface normal vector /// @param norm [out] surface normal vector
/// @param iend [out] ray end integer position (voxel position + normal) /// @param iend [out] ray end integer position (voxel position + normal)
/// @param filter filtered ids /// @param filter filtered ids
/// @param includeNonSelectable will non-selectable blocks be included
/// @return voxel pointer or nullptr /// @return voxel pointer or nullptr
voxel* raycast( voxel* raycast(
const Chunks& chunks, const Chunks& chunks,
@ -428,7 +429,8 @@ voxel* raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter std::set<blockid_t> filter,
bool includeNonSelectable
); );
/// @brief Cast ray to a selectable block with filter based on id. /// @brief Cast ray to a selectable block with filter based on id.
@ -440,6 +442,7 @@ voxel* raycast(
/// @param norm [out] surface normal vector /// @param norm [out] surface normal vector
/// @param iend [out] ray end integer position (voxel position + normal) /// @param iend [out] ray end integer position (voxel position + normal)
/// @param filter filtered ids /// @param filter filtered ids
/// @param includeNonSelectable will non-selectable blocks be included
/// @return voxel pointer or nullptr /// @return voxel pointer or nullptr
voxel* raycast( voxel* raycast(
const GlobalChunks& chunks, const GlobalChunks& chunks,
@ -449,7 +452,8 @@ voxel* raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter std::set<blockid_t> filter,
bool includeNonSelectable
); );
void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false); void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false);

View file

@ -332,8 +332,8 @@ private:
GLFWwindow* window; GLFWwindow* window;
bool cursorLocked = false; bool cursorLocked = false;
bool cursorDrag = false; bool cursorDrag = false;
glm::vec2 delta; glm::vec2 delta {};
glm::vec2 cursor; glm::vec2 cursor {};
}; };
static_assert(!std::is_abstract<GLFWInput>()); static_assert(!std::is_abstract<GLFWInput>());
@ -577,7 +577,7 @@ private:
CursorShape cursor = CursorShape::ARROW; CursorShape cursor = CursorShape::ARROW;
int framerate = -1; int framerate = -1;
std::stack<glm::vec4> scissorStack; std::stack<glm::vec4> scissorStack;
glm::vec4 scissorArea; glm::vec4 scissorArea {};
double prevSwap = 0.0; double prevSwap = 0.0;
int posX = 0; int posX = 0;
int posY = 0; int posY = 0;