Merge branch 'main' into environments

This commit is contained in:
MihailRis 2026-06-06 00:33:56 +03:00
commit a557068b90
54 changed files with 780 additions and 539 deletions

View file

@ -149,16 +149,22 @@ gui.load_document(
args: table args: table
) -> str ) -> str
-- Loads a template into the layout -- Loads and processes layout template from file
gui.template( gui.template(
-- template name in /layouts/templates without path and extension -- template name in /layouts/templates without path and extension
name: str, name: str,
-- variable table (can be used in markup) -- variable table (can be used in markup)
-- * Ex: <label>%{text}</label> -- * Ex: <label>%{text}</label>
-- * text in this case is the value from params with the text key -- * text in this case is the value from params with the text key
params: table, params: table
-- table, available in events as the global variable DATA ) -> str
[optional] data: table
-- Processes layout template from string
gui.process_template(
-- template source code
source: str,
-- variable table (same as gui.template)
params: table
) -> str ) -> str
``` ```

View file

@ -1,13 +1,21 @@
# *time* library # *time* Library
```python ```lua
time.uptime() -> float -- Returns the time since engine startup in seconds and milliseconds.
time.uptime() -> number
-- Returns the time delta in seconds and milliseconds (time elapsed since the previous frame)
time.delta() -> number
-- Returns UTC time in seconds
time.utc_time() -> int
-- Returns local (system) time in seconds
time.local_time() -> int
-- Returns the local time offset from UTC in seconds
time.utc_offset() -> int
-- Returns high-precision time in seconds (elapsed since the engine startup).
time.precise_time() -> number
``` ```
Returns time elapsed since the engine started.
```python
time.delta() -> float
```
Returns time elapsed since the last frame.

View file

@ -147,17 +147,23 @@ gui.load_document(
args: table -- параметры для события on_open args: table -- параметры для события on_open
) -> string ) -> string
-- Загружает шаблон в лояут -- Обрабатывает xml шаблон макета из файла
gui.template( gui.template(
-- имя шаблона в /layouts/templates без пути и расширения -- имя шаблона в /layouts/templates без пути и расширения
name: string, name: string,
-- таблица переменных (может быть использована в разметке) -- таблица переменных (может быть использована в разметке)
-- * Пр: <label>%{text}</label> -- * Пр: <label>%{text}</label>
-- * text в данном случае, это значение из params по ключу text -- * text в данном случае, это значение из params по ключу text
params: table, params: table
-- таблица, доступная в событиях как глобальная переменная DATA
[опционально] data: table
) -> string ) -> string
-- Обрабатывает xml шаблон макета из строки
gui.process_template(
-- шаблон в виде строки
source: string,
-- таблица переменных, как в gui.template
params: table
)
``` ```
## Корневой документ ## Корневой документ

View file

@ -15,4 +15,7 @@ time.local_time() -> int
-- Возвращает смещение локального времени от UTC в секундах -- Возвращает смещение локального времени от UTC в секундах
time.utc_offset() -> int time.utc_offset() -> int
-- Возвращает время высокой точности в секундах. За точку отсчёта берётся запуск движка.
time.precise_time() -> number
``` ```

View file

@ -8,5 +8,5 @@ multiline-string-end = "]]"
keywords = [ keywords = [
"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "and", "break", "do", "else", "elseif", "end", "false", "for", "function",
"if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true",
"until", "while", "self", "error" "until", "while", "self", "error", "require"
] ]

View file

@ -1,4 +1,4 @@
function create_setting(id, name, step, postfix) function create_trackbar_setting(id, name, step, postfix)
local info = app.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", {
@ -10,10 +10,10 @@ function create_setting(id, name, step, postfix)
step=step, step=step,
postfix=postfix postfix=postfix
})) }))
update_setting(app.get_setting(id), id, name, postfix) update_trackbar_label(app.get_setting(id), id, name, postfix)
end end
function update_setting(x, id, name, postfix) function update_trackbar_label(x, id, name, postfix)
app.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(
@ -24,12 +24,22 @@ function update_setting(x, id, name, postfix)
) )
end end
function update_checkbox_setting(id, value)
app.set_setting(id, value)
local recording_enabled = app.get_setting("audio.recording-enabled")
document.input_device_select.enabled = recording_enabled
document.input_volume_inner.enabled = recording_enabled
document.input_volume_outer.enabled = recording_enabled
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) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>", "<checkbox consumer='function(x) update_checkbox_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
)) ))
update_checkbox_setting(id, app.get_setting(id))
end end
local initialized = false local initialized = false
@ -55,11 +65,12 @@ function on_open()
prev_amplitude = amplitude prev_amplitude = amplitude
end) end)
end end
create_setting("audio.volume-master", "Master Volume", 0.01) create_trackbar_setting("audio.volume-master", "Master Volume", 0.01)
create_setting("audio.volume-regular", "Regular Sounds", 0.01) create_trackbar_setting("audio.volume-regular", "Regular Sounds", 0.01)
create_setting("audio.volume-ui", "UI Sounds", 0.01) create_trackbar_setting("audio.volume-ui", "UI Sounds", 0.01)
create_setting("audio.volume-ambient", "Ambient", 0.01) create_trackbar_setting("audio.volume-ambient", "Ambient", 0.01)
create_setting("audio.volume-music", "Music", 0.01) create_trackbar_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) app.set_setting(\"audio.input-device\", opt) end'/>") "onselect='function(opt) app.set_setting(\"audio.input-device\", opt) end'/>")
@ -67,9 +78,7 @@ function on_open()
.."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>" .."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>"
.."</container>") .."</container>")
local selectbox = document.input_device_select local selectbox = document.input_device_select
local devices = { local devices = {}
{value="none", text=gui.str("None", "settings.microphone")},
}
local names = audio.__get_input_devices_names() local names = audio.__get_input_devices_names()
for i, name in ipairs(names) do for i, name in ipairs(names) do
table.insert(devices, {value=name, text=name}) table.insert(devices, {value=name, text=name})
@ -78,7 +87,10 @@ function on_open()
local info = audio.input.get_input_info() local info = audio.input.get_input_info()
if info then if info then
selectbox.value = info.device_specifier selectbox.value = info.device_specifier
else
selectbox.value = app.get_setting("audio.input-device")
end end
create_checkbox("audio.recording-enabled", "Microphone access", "audio.recording-enabled.tooltip")
create_checkbox("audio.acoustic-effects", "Acoustic effects", "audio.acoustic-effects.tooltip") create_checkbox("audio.acoustic-effects", "Acoustic effects", "audio.acoustic-effects.tooltip")
end end

View file

@ -8,7 +8,7 @@
<textbox id='search_textbox' multiline='false' size='300,20' sub-consumer='function(x) refresh_search() end'></textbox> <textbox id='search_textbox' multiline='false' size='300,20' sub-consumer='function(x) refresh_search() end'></textbox>
<checkbox size='100,24' id='search_key_checkbox' consumer='function(x) refresh_search() end'>@settings.Controls Search Mode</checkbox> <checkbox size='100,24' id='search_key_checkbox' consumer='function(x) refresh_search() end'>@settings.Controls Search Mode</checkbox>
</panel> </panel>
<panel id='bindings_panel' size='380,204' padding='2' interval='1' max-length='300' color='#0000004C'> <panel id='bindings_panel' size='380,204' padding='2' interval='1' max-length='333' color='#0000004C'>
<!-- content is generated in script --> <!-- content is generated in script -->
</panel> </panel>
</panel> </panel>

View file

@ -9,7 +9,7 @@ tostring_overrides["display.framerate"] = function(x)
end end
end end
function create_setting(id, name, step, postfix, tooltip, changeonrelease) function create_trackbar_setting(id, name, step, postfix, tooltip, changeonrelease)
local info = app.get_setting_info(id) local info = app.get_setting_info(id)
postfix = postfix or "" postfix = postfix or ""
tooltip = tooltip or "" tooltip = tooltip or ""
@ -25,10 +25,10 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease)
tooltip=tooltip, tooltip=tooltip,
changeonrelease=changeonrelease changeonrelease=changeonrelease
})) }))
update_setting(app.get_setting(id), id, name, postfix) update_trackbar_label(app.get_setting(id), id, name, postfix)
end end
function update_setting(x, id, name, postfix) function update_trackbar_label(x, id, name, postfix)
local str local str
local func = tostring_overrides[id] local func = tostring_overrides[id]
if func then if func then
@ -55,8 +55,8 @@ end
function on_open() function on_open()
create_setting("camera.fov", "FOV", 1, "°") create_trackbar_setting("camera.fov", "FOV", 1, "°")
create_setting("display.framerate", "Framerate", 1, "", "", true) create_trackbar_setting("display.framerate", "Framerate", 1, "", "", true)
document.root:add(string.format( document.root:add(string.format(
"<select context='settings' onselect='function(opt) app.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'>"..
@ -70,5 +70,5 @@ function on_open()
create_checkbox("camera.inertia", "Camera Inertia") create_checkbox("camera.inertia", "Camera Inertia")
create_checkbox("camera.fov-effects", "Camera FOV Effects") create_checkbox("camera.fov-effects", "Camera FOV Effects")
create_checkbox("display.limit-fps-iconified", "Limit Background FPS") create_checkbox("display.limit-fps-iconified", "Limit Background FPS")
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip") create_trackbar_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
end end

View file

@ -1,4 +1,4 @@
function create_setting(id, name, step, postfix, tooltip, changeonrelease) function create_trackbar_setting(id, name, step, postfix, tooltip, changeonrelease)
local info = app.get_setting_info(id) local info = app.get_setting_info(id)
postfix = postfix or "" postfix = postfix or ""
tooltip = tooltip or "" tooltip = tooltip or ""
@ -14,10 +14,10 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease)
tooltip=tooltip, tooltip=tooltip,
changeonrelease=changeonrelease changeonrelease=changeonrelease
})) }))
update_setting(app.get_setting(id), id, name, postfix) update_trackbar_label(app.get_setting(id), id, name, postfix)
end end
function update_setting(x, id, name, postfix) function update_trackbar_label(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",
@ -36,15 +36,15 @@ function create_checkbox(id, name, tooltip)
end end
function on_open() function on_open()
create_setting("chunks.load-distance", "Load Distance", 1) create_trackbar_setting("chunks.load-distance", "Load Distance", 1)
create_setting("chunks.load-speed", "Load Speed", 1) create_trackbar_setting("chunks.load-speed", "Load Speed", 1)
create_setting("graphics.fog-curve", "Fog Curve", 0.1) create_trackbar_setting("graphics.fog-curve", "Fog Curve", 0.1)
create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip")
create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip") create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip")
create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip")
create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip") create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip")
create_setting("graphics.ssao", "SSAO", 1, "", "graphics.ssao.tooltip") create_trackbar_setting("graphics.ssao", "SSAO", 1, "", "graphics.ssao.tooltip")
create_setting("graphics.shadows-quality", "Shadows quality", 1) create_trackbar_setting("graphics.shadows-quality", "Shadows quality", 1)
create_setting("graphics.clouds-quality", "Clouds quality", 1) create_trackbar_setting("graphics.clouds-quality", "Clouds quality", 1)
end end

View file

@ -3,5 +3,5 @@
<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) app.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_trackbar_label(x, "%{id}", "%{name}", "%{postfix}") end'/>
</panel> </panel>

View file

@ -215,6 +215,13 @@ FFIBytearray = {
} }
table.merge(FFIBytearray, bytearray_methods) table.merge(FFIBytearray, bytearray_methods)
local function FFIBytearray_as_ptr(bytes)
if type(bytes) == "cdata" then
return tostring(bytes.bytes):sub(27), bytes.size
end
return "0"
end
local function FFIBytearray_as_string(bytes) local function FFIBytearray_as_string(bytes)
local t = type(bytes) local t = type(bytes)
if t == "cdata" then if t == "cdata" then
@ -283,6 +290,7 @@ local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4)
return { return {
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
FFIBytearray_as_string = FFIBytearray_as_string, FFIBytearray_as_string = FFIBytearray_as_string,
FFIBytearray_as_ptr = FFIBytearray_as_ptr,
FFIU16view = FFIU16view, FFIU16view = FFIU16view,
FFII16view = FFII16view, FFII16view = FFII16view,
FFIU32view = FFIU32view, FFIU32view = FFIU32view,

View file

@ -0,0 +1,62 @@
--- Console library extension ---
console.cheats = {}
local log_element = Document.new("core:console").log
function console.log(...)
local args = {...}
local text = ''
for i,v in ipairs(args) do
if i ~= 1 then
text = text..' '..v
else
text = text..v
end
end
log_element.caret = -1
if log_element.caret > 0 then
text = '\n'..text
end
log_element:paste(text)
end
local console_add_command = console.__add_command
console.__add_command = nil
function console.add_command(scheme, description, handler, is_cheat)
console_add_command(scheme, description, handler)
if not is_cheat then return end
local name = string.match(scheme, "^(%S+)")
if not name then
error("Incorrect command syntax, command name not found")
end
table.insert_unique(console.cheats, name)
end
function console.is_cheat(name)
if not table.has(console.get_commands_list(), name) then
error(string.format("command \"%s\" not found", name))
end
return table.has(console.cheats, name)
end
function console.set_cheat(name, status)
local is_cheat = console.is_cheat(name)
if status and not is_cheat then
table.insert(console.cheats, name)
return true
elseif not status and is_cheat then
table.remove_value(console.cheats, name)
return true
end
return false
end
function console.chat(...)
console.log(...)
events.emit("core:chat", ...)
end

View file

@ -57,6 +57,18 @@ function table.merge(t1, t2)
return t1 return t1
end end
function table.extend(t1, t2)
for i, v in pairs(t2) do
if type(i) == "number" then
t1[#t1 + 1] = v
else
t1[i] = v
end
end
return t1
end
function table.map(t, func) function table.map(t, func)
for i, v in pairs(t) do for i, v in pairs(t) do
t[i] = func(i, v) t[i] = func(i, v)

View file

@ -0,0 +1,229 @@
local __app = __vc_app
local __rules = rules
local function configure_SSAO()
-- Temporary using slot to configure built-in SSAO effect
local slot = gfx.posteffects.index("core:ssao")
gfx.posteffects.set_effect(slot, "ssao")
-- Generating random SSAO samples
local buffer = Bytearray(0)
for i = 0, 63 do
local x = math.random() * 2.0 - 1.0
local y = math.random() * 2.0 - 1.0
local z = math.random() * 2.0
local len = math.sqrt(x * x + y * y + z * z)
if len > 0 then
x = x / len
y = y / len
z = z / len
end
Bytearray.append(buffer, byteutil.pack("fff", x, y, z))
end
gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer))
local function update_ssao_quality(value)
value = math.min(value, 3)
gfx.posteffects.set_params(slot, {
u_kernelSize = value * 16,
u_radius = 0.4 / value,
u_bias = 0.006 / value / value,
})
end
events.on("core:setting.graphics.ssao.set", update_ssao_quality)
update_ssao_quality(__app.get_setting("graphics.ssao"))
end
local function __vc_on_hud_open()
local _hud_is_content_access = hud._is_content_access
local _hud_set_content_access = hud._set_content_access
local _hud_set_debug_cheats = hud._set_debug_cheats
__rules.create("allow-cheats", true)
__rules.create("allow-content-access", _hud_is_content_access(), function(value)
_hud_set_content_access(value)
end)
__rules.create("allow-flight", true, function(value)
input.set_enabled("player.flight", value)
end)
__rules.create("allow-noclip", true, function(value)
input.set_enabled("player.noclip", value)
end)
__rules.create("allow-attack", true, function(value)
input.set_enabled("player.attack", value)
end)
__rules.create("allow-destroy", true, function(value)
input.set_enabled("player.destroy", value)
end)
__rules.create("allow-cheat-movement", true, function(value)
input.set_enabled("movement.cheat", value)
end)
__rules.create("allow-fast-interaction", true, function(value)
input.set_enabled("player.fast_interaction", value)
end)
__rules.create("allow-debug-cheats", true, function(value)
_hud_set_debug_cheats(value)
end)
input.add_callback("devtools.console", function()
if menu.page ~= "" then
return
end
time.post_runnable(function()
hud.show_overlay("core:console", false, {"console"})
end)
end)
input.add_callback("hud.chat", function()
if menu.page ~= "" then
return
end
time.post_runnable(function()
hud.show_overlay("core:console", false, {"chat"})
end)
end)
input.add_callback("key:escape", function()
if menu.page ~= "" then
if not menu:back() then
menu:reset()
gui.set_active_frame("")
end
elseif hud.is_inventory_open() then
hud.close_inventory()
elseif gui.get_active_frame() then
gui.set_active_frame("")
else
hud.pause()
end
end)
hud.open_permanent("core:ingame_chat")
configure_SSAO()
end
local Schedule = require "core:schedule"
local ScheduleGroup_mt = {
__index = {
publish = function(self, schedule)
local id = self._next_schedule
self._schedules[id] = schedule
self._next_schedule = id + 1
end,
tick = function(self, dt)
for id, schedule in pairs(self._schedules) do
schedule:tick(dt)
end
self.common:tick(dt)
end,
remove = function(self, id)
self._schedules[id] = nil
end,
}
}
local function ScheduleGroup()
return setmetatable({
_next_schedule = 1,
_schedules = {},
common = Schedule()
}, ScheduleGroup_mt)
end
local RULES_FILE = "world:rules.toml"
local function __vc_on_world_open()
time.schedules.world = ScheduleGroup()
if not file.exists(RULES_FILE) then
return
end
local rule_values = toml.parse(file.read(RULES_FILE))
for name, value in pairs(rule_values) do
__rules.set(name, value)
end
end
local function __vc_on_world_tick(tps)
time.schedules.world:tick(1.0 / tps)
end
local function __vc_process_before_quit()
block.__process_register_events()
end
local function __vc_on_world_save()
local rule_values = {}
for name, rule in pairs(__rules.rules) do
rule_values[name] = rule.value
end
file.write(RULES_FILE, toml.tostring(rule_values))
end
local __close_all_descriptors = file.__close_all_descriptors
local __gui_util_reset_local = gui_util.__reset_local
local __stdcomp_reset = stdcomp.__reset
file.__close_all_descriptors = nil
gui_util.__reset_local = nil
stdcomp.reset = nil
local function __vc_on_world_quit()
__rules.clear()
__gui_util_reset_local()
__stdcomp_reset()
__close_all_descriptors()
end
local __post_runnables = {}
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
audio.__reset_fetch_buffer = nil
local __vc_update_coroutines = _G.__vc_update_coroutines
local function __process_post_runnables()
if #__post_runnables > 0 then
for _, func in ipairs(__post_runnables) do
local status, result = xpcall(func, __vc__error)
if not status then
debug.error("error in post_runnable: "..result)
end
end
__post_runnables = {}
end
__vc_update_coroutines()
fn_audio_reset_fetch_buffer()
debug.pull_events()
network.__process_events()
if not hud or not hud.is_paused() then
block.__process_register_events()
block.__perform_ticks(time.delta())
end
end
local __vc__is_post_runnable = false
function __vc__process_post_runnables()
__vc__is_post_runnable = true
local success, err = pcall(__process_post_runnables)
if not success then
debug.error("an error ocurred while processing post-runnables: ".. err)
end
__vc__is_post_runnable = false
end
function time.post_runnable(runnable)
table.insert(__post_runnables, runnable)
end
return {
__vc_on_hud_open = __vc_on_hud_open,
__vc_on_world_open = __vc_on_world_open,
__vc_on_world_tick = __vc_on_world_tick,
__vc_process_before_quit = __vc_process_before_quit,
__vc_on_world_save = __vc_on_world_save,
__vc_on_world_quit = __vc_on_world_quit,
__vc__process_post_runnables = __vc__process_post_runnables,
__vc_is_post_runnable_context = function()
return __vc__is_post_runnable
end
}

View file

@ -133,6 +133,9 @@ console.add_command(
"Fill specified zone with blocks", "Fill specified zone with blocks",
function(args, kwargs) function(args, kwargs)
local name, x1,y1,z1, x2,y2,z2 = unpack(args) local name, x1,y1,z1, x2,y2,z2 = unpack(args)
x1, x2 = math.min(x1, x2), math.max(x1, x2)
y1, y2 = math.min(y1, y2), math.max(y1, y2)
z1, z2 = math.min(z1, z2), math.max(z1, z2)
local id = block.index(name) local id = block.index(name)
for y=y1,y2 do for y=y1,y2 do
for z=z1,z2 do for z=z1,z2 do

View file

@ -12,7 +12,7 @@ function sleep(timesec)
end end
end end
function tb_frame_tostring(frame) local function tb_frame_tostring(frame)
local s = frame.short_src local s = frame.short_src
if frame.what ~= "C" then if frame.what ~= "C" then
s = s .. ":" .. tostring(frame.currentline) s = s .. ":" .. tostring(frame.currentline)
@ -26,7 +26,6 @@ function tb_frame_tostring(frame)
end end
local __vc__app_script_coroutine local __vc__app_script_coroutine
local __vc__is_post_runnable = false
local function complete_app_lib(app) local function complete_app_lib(app)
local __app_load_content = app.load_content local __app_load_content = app.load_content
@ -46,7 +45,7 @@ local function complete_app_lib(app)
app.tick = __app_tick app.tick = __app_tick
local function call_in_app_script_co(func, ...) local function call_in_app_script_co(func, ...)
if __vc__is_post_runnable then if __vc_is_post_runnable_context() then
func(...) func(...)
return return
end end
@ -192,6 +191,22 @@ function start_coroutine(chunk, name)
__vc_named_coroutines[name] = co __vc_named_coroutines[name] = co
end end
function __vc_update_coroutines()
local dead = {}
for name, co in pairs(__vc_named_coroutines) do
local success, err = coroutine.resume(co)
if not success then
debug.error(err)
end
if coroutine.status(co) == "dead" then
table.insert(dead, name)
end
end
for _, name in ipairs(dead) do
__vc_named_coroutines[name] = nil
end
end
function __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)
@ -257,71 +272,8 @@ do
end end
end end
--- Console library extension --- function gui.process_template(source, params)
console.cheats = {} local text = source:gsub("%%{([^}]+)}", function(n)
local log_element = Document.new("core:console").log
function console.log(...)
local args = {...}
local text = ''
for i,v in ipairs(args) do
if i ~= 1 then
text = text..' '..v
else
text = text..v
end
end
log_element.caret = -1
if log_element.caret > 0 then
text = '\n'..text
end
log_element:paste(text)
end
local console_add_command = console.__add_command
console.__add_command = nil
function console.add_command(scheme, description, handler, is_cheat)
console_add_command(scheme, description, handler)
if not is_cheat then return end
local name = string.match(scheme, "^(%S+)")
if not name then
error("Incorrect command syntax, command name not found")
end
table.insert_unique(console.cheats, name)
end
function console.is_cheat(name)
if not table.has(console.get_commands_list(), name) then
error(string.format("command \"%s\" not found", name))
end
return table.has(console.cheats, name)
end
function console.set_cheat(name, status)
local is_cheat = console.is_cheat(name)
if status and not is_cheat then
table.insert(console.cheats, name)
return true
elseif not status and is_cheat then
table.remove_value(console.cheats, name)
return true
end
return false
end
function console.chat(...)
console.log(...)
events.emit("core:chat", ...)
end
function gui.template(name, params)
local text = file.read(file.find("layouts/templates/"..name..".xml"))
text = text:gsub("%%{([^}]+)}", function(n)
local s = params[n] local s = params[n]
if s == nil then if s == nil then
return return
@ -341,6 +293,11 @@ function gui.template(name, params)
return text return text
end end
function gui.template(name, params)
local text = file.read(file.find("layouts/templates/"..name..".xml"))
return gui.process_template(text, params)
end
session = require "core:internal/session" session = require "core:internal/session"
stdcomp = require "core:internal/stdcomp" stdcomp = require "core:internal/stdcomp"
entities.get = stdcomp.get_Entity entities.get = stdcomp.get_Entity
@ -378,236 +335,20 @@ end
math.randomseed(time.uptime() * 1536227939) math.randomseed(time.uptime() * 1536227939)
rules = require "core:internal/rules" rules = require "core:internal/rules"
local _rules = rules
local function configure_SSAO()
-- Temporary using slot to configure built-in SSAO effect
local slot = gfx.posteffects.index("core:ssao")
gfx.posteffects.set_effect(slot, "ssao")
-- Generating random SSAO samples
local buffer = Bytearray(0)
for i = 0, 63 do
local x = math.random() * 2.0 - 1.0
local y = math.random() * 2.0 - 1.0
local z = math.random() * 2.0
local len = math.sqrt(x * x + y * y + z * z)
if len > 0 then
x = x / len
y = y / len
z = z / len
end
Bytearray.append(buffer, byteutil.pack("fff", x, y, z))
end
gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer))
local function update_ssao_quality(value)
value = math.min(value, 3)
gfx.posteffects.set_params(slot, {
u_kernelSize = value * 16,
u_radius = 0.4 / value,
u_bias = 0.006 / value / value,
})
end
events.on("core:setting.graphics.ssao.set", update_ssao_quality)
update_ssao_quality(__app.get_setting("graphics.ssao"))
end
function __vc_on_hud_open()
local _hud_is_content_access = hud._is_content_access
local _hud_set_content_access = hud._set_content_access
local _hud_set_debug_cheats = hud._set_debug_cheats
_rules.create("allow-cheats", true)
_rules.create("allow-content-access", _hud_is_content_access(), function(value)
_hud_set_content_access(value)
end)
_rules.create("allow-flight", true, function(value)
input.set_enabled("player.flight", value)
end)
_rules.create("allow-noclip", true, function(value)
input.set_enabled("player.noclip", value)
end)
_rules.create("allow-attack", true, function(value)
input.set_enabled("player.attack", value)
end)
_rules.create("allow-destroy", true, function(value)
input.set_enabled("player.destroy", value)
end)
_rules.create("allow-cheat-movement", true, function(value)
input.set_enabled("movement.cheat", value)
end)
_rules.create("allow-fast-interaction", true, function(value)
input.set_enabled("player.fast_interaction", value)
end)
_rules.create("allow-debug-cheats", true, function(value)
_hud_set_debug_cheats(value)
end)
input.add_callback("devtools.console", function()
if menu.page ~= "" then
return
end
time.post_runnable(function()
hud.show_overlay("core:console", false, {"console"})
end)
end)
input.add_callback("hud.chat", function()
if menu.page ~= "" then
return
end
time.post_runnable(function()
hud.show_overlay("core:console", false, {"chat"})
end)
end)
input.add_callback("key:escape", function()
if menu.page ~= "" then
if not menu:back() then
menu:reset()
gui.set_active_frame("")
end
elseif hud.is_inventory_open() then
hud.close_inventory()
elseif gui.get_active_frame() then
gui.set_active_frame("")
else
hud.pause()
end
end)
hud.open_permanent("core:ingame_chat")
configure_SSAO()
end
local Schedule = require "core:schedule"
local ScheduleGroup_mt = {
__index = {
publish = function(self, schedule)
local id = self._next_schedule
self._schedules[id] = schedule
self._next_schedule = id + 1
end,
tick = function(self, dt)
for id, schedule in pairs(self._schedules) do
schedule:tick(dt)
end
self.common:tick(dt)
end,
remove = function(self, id)
self._schedules[id] = nil
end,
}
}
local function ScheduleGroup()
return setmetatable({
_next_schedule = 1,
_schedules = {},
common = Schedule()
}, ScheduleGroup_mt)
end
time.schedules = {} time.schedules = {}
local RULES_FILE = "world:rules.toml" table.merge(_G, require "core:internal/lifetime_events")
function __vc_on_world_open()
time.schedules.world = ScheduleGroup()
if not file.exists(RULES_FILE) then
return
end
local rule_values = toml.parse(file.read(RULES_FILE))
for name, value in pairs(rule_values) do
_rules.set(name, value)
end
end
function __vc_on_world_tick(tps)
time.schedules.world:tick(1.0 / tps)
end
function __vc_process_before_quit()
block.__process_register_events()
end
function __vc_on_world_save()
local rule_values = {}
for name, rule in pairs(rules.rules) do
rule_values[name] = rule.value
end
file.write(RULES_FILE, toml.tostring(rule_values))
end
local __close_all_descriptors = file.__close_all_descriptors
local __gui_util_reset_local = gui_util.__reset_local
local __stdcomp_reset = stdcomp.__reset
file.__close_all_descriptors = nil
gui_util.__reset_local = nil
stdcomp.reset = nil
function __vc_on_world_quit()
_rules.clear()
__gui_util_reset_local()
__stdcomp_reset()
__close_all_descriptors()
end
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
audio.__reset_fetch_buffer = nil
core.get_core_token = audio.input.__get_core_token core.get_core_token = audio.input.__get_core_token
local __post_runnables = {} require "core:internal/console"
require "core:internal/deprecated"
local function __process_post_runnables()
if #__post_runnables > 0 then
for _, func in ipairs(__post_runnables) do
local status, result = xpcall(func, __vc__error)
if not status then
debug.error("error in post_runnable: "..result)
end
end
__post_runnables = {}
end
local dead = {} ------------------------------------------
for name, co in pairs(__vc_named_coroutines) do ---- Unsafe functions cleanup section ----
local success, err = coroutine.resume(co) ------------------------------------------
if not success then
debug.error(err)
end
if coroutine.status(co) == "dead" then
table.insert(dead, name)
end
end
for _, name in ipairs(dead) do
__vc_named_coroutines[name] = nil
end
fn_audio_reset_fetch_buffer()
debug.pull_events()
network.__process_events()
if not hud or not hud.is_paused() then
block.__process_register_events()
block.__perform_ticks(time.delta())
end
end
function __vc__process_post_runnables()
__vc__is_post_runnable = true
local success, err = pcall(__process_post_runnables)
if not success then
debug.error("an error ocurred while processing post-runnables: ".. err)
end
__vc__is_post_runnable = false
end
function time.post_runnable(runnable)
table.insert(__post_runnables, runnable)
end
-- Hide unsafe debug.* functions
local removed_names = { local removed_names = {
"getregistry", "getupvalue", "setupvalue", "upvalueid", "upvaluejoin", "getregistry", "getupvalue", "setupvalue", "upvalueid", "upvaluejoin",
"sethook", "gethook", "getinfo" "sethook", "gethook", "getinfo"
@ -626,9 +367,9 @@ debug.getinfo = function(lvl, fields)
return debuginfo return debuginfo
end end
require "core:internal/deprecated"
ffi = nil ffi = nil
__vc_app = nil __vc_app = nil
__vc_lock_internal_modules() __vc_lock_internal_modules()
__vc_lock_internal_modules = nil __vc_lock_internal_modules = nil
__vc_update_coroutines = nil
__VC_SCRIPT_NAME = ""

View file

@ -1,13 +1,18 @@
local _vc_headless = __VC_HEADLESS local _vc_headless = __VC_HEADLESS
local _vc_project_args = __VC_PROJECT_ARGS
__VC_HEADLESS = nil __VC_HEADLESS = nil
__VC_PROJECT_ARGS = nil
vc = { vc = {
is_headless = function() is_headless = function()
return _vc_headless return _vc_headless
end, end,
is_client = function () is_client = function()
return not _vc_headless return not _vc_headless
end end,
get_setting = __vc_app.get_setting,
get_setting_str = __vc_app.get_setting_str,
get_setting_info = __vc_app.get_setting_info,
} }
local _ffi = ffi local _ffi = ffi
@ -297,9 +302,14 @@ require "core:internal/extensions/file"
require "core:internal/extensions/table" require "core:internal/extensions/table"
require "core:internal/extensions/string" require "core:internal/extensions/string"
vc.get_project_arg = function (name)
return _vc_project_args[name]
end
local bytearray = require "core:internal/bytearray" local bytearray = require "core:internal/bytearray"
Bytearray = bytearray.FFIBytearray Bytearray = bytearray.FFIBytearray
Bytearray_as_string = bytearray.FFIBytearray_as_string Bytearray_as_string = bytearray.FFIBytearray_as_string
Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr
U16view = bytearray.FFIU16view U16view = bytearray.FFIU16view
I16view = bytearray.FFII16view I16view = bytearray.FFII16view
U32view = bytearray.FFIU32view U32view = bytearray.FFIU32view

View file

@ -18,6 +18,7 @@ devtools.output=Output
# Tooltips # Tooltips
audio.acoustic-effects.tooltip=Acoustic effects may not be supported by your system audio.acoustic-effects.tooltip=Acoustic effects may not be supported by your system
audio.recording-enabled.tooltip=Enables audio recording features
graphics.gamma.tooltip=Lighting brightness curve graphics.gamma.tooltip=Lighting brightness curve
graphics.backlight.tooltip=Backlight to prevent total darkness graphics.backlight.tooltip=Backlight to prevent total darkness
graphics.dense-render.tooltip=Enables transparency in blocks like leaves graphics.dense-render.tooltip=Enables transparency in blocks like leaves

View file

@ -39,6 +39,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/
# Подсказки # Подсказки
audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой
audio.recording-enabled.tooltip=Включает функции записи звука
graphics.gamma.tooltip=Кривая яркости освещения graphics.gamma.tooltip=Кривая яркости освещения
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья
@ -114,6 +115,7 @@ settings.Microphone=Микрофон
settings.microphone.None=Нет settings.microphone.None=Нет
settings.Clouds quality=Качество облаков settings.Clouds quality=Качество облаков
settings.Acoustic effects=Акустические эффекты settings.Acoustic effects=Акустические эффекты
settings.Microphone access=Доступ к микрофону
# Управление # Управление
chunks.reload=Перезагрузить Чанки chunks.reload=Перезагрузить Чанки

View file

@ -711,7 +711,7 @@ std::unique_ptr<ALAudio> ALAudio::create(const AudioSettings& settings) {
return nullptr; return nullptr;
} }
bool effects = true; bool effects = true;
ALint attribs[4] {}; [[maybe_unused]] ALint attribs[4] {};
if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) { if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) {
logger.info() << "AL effects extension present"; logger.info() << "AL effects extension present";
attribs[0] = ALC_MAX_AUXILIARY_SENDS; attribs[0] = ALC_MAX_AUXILIARY_SENDS;

View file

@ -190,18 +190,34 @@ void audio::initialize(
audio::get_channel(channel.name)->setVolume(value * value); audio::get_channel(channel.name)->setVolume(value * value);
}, true)); }, true));
} }
objects_keeper.keepAlive(settings.acousticEffects.observe([=](auto value) { objects_keeper.keepAlive(settings.acousticEffects.observe([=](bool value) {
if (value) return; if (value) return;
backend->setAcoustics(audio::Acoustics {}); backend->setAcoustics(audio::Acoustics {});
}, true)); }, true));
if (inputEnabled) { ::input_enabled = inputEnabled;
::input_device = backend->openInputDevice("", 44100, 1, 16);
::input_enabled = true; objects_keeper.keepAlive(settings.recordingEnabled.observe([&settings](bool enabled) {
} if (!::input_enabled) {
if (::input_device) { return;
::input_device->startCapture(); }
} if (!enabled && ::input_device == nullptr) {
return;
}
if (enabled) {
logger.info() << "recording enabled; input device is "
<< settings.inputDevice.get();
::input_device = backend->openInputDevice(
settings.inputDevice.get(), 44100, 1, 16
);
::input_device->startCapture();
} else {
if (::input_device) {
::input_device->stopCapture();
}
::input_device = nullptr;
}
}, true));
} }
InputDevice* audio::get_input_device() { InputDevice* audio::get_input_device() {

View file

@ -21,6 +21,10 @@ inline bool is_identifier_part(int c) {
return is_identifier_start(c) || is_digit(c) || c == '-'; return is_identifier_start(c) || is_digit(c) || c == '-';
} }
inline bool is_minimal_identifier_part(int c) {
return is_identifier_start(c) || is_digit(c);
}
inline bool is_json_identifier_start(int c) { inline bool is_json_identifier_start(int c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' ||
c == '.'; c == '.';

View file

@ -93,7 +93,7 @@ public:
throw error("identifier expected"); throw error("identifier expected");
} }
int start = pos; int start = pos;
while (hasNext() && is_identifier_part(source[pos])) { while (hasNext() && is_minimal_identifier_part(source[pos])) {
pos++; pos++;
} }
return std::wstring(source.substr(start, pos - start)); return std::wstring(source.substr(start, pos - start));

View file

@ -55,12 +55,12 @@ model::Model VcmModel::squashed() const {
} }
static const std::unordered_map<std::string, int> side_indices { static const std::unordered_map<std::string, int> side_indices {
{"north", 0}, {"east", 0},
{"south", 1}, {"west", 1},
{"top", 2}, {"bottom", 2},
{"bottom", 3}, {"top", 3},
{"west", 4}, {"south", 4},
{"east", 5}, {"north", 5},
}; };
static bool to_boolean(const xml::Attribute& attr) { static bool to_boolean(const xml::Attribute& attr) {

View file

@ -2,6 +2,7 @@
#include <string> #include <string>
#include <filesystem> #include <filesystem>
#include <unordered_map>
struct CoreParameters { struct CoreParameters {
bool headless = false; bool headless = false;
@ -13,4 +14,5 @@ struct CoreParameters {
std::filesystem::path projectFolder; std::filesystem::path projectFolder;
std::string debugServerString; std::string debugServerString;
int tps = 20; int tps = 20;
std::unordered_map<std::string, std::string> projectArgs;
}; };

View file

@ -189,7 +189,7 @@ void Engine::initialize(CoreParameters coreParameters) {
keepAlive(settings.audio.inputDevice.observe([](auto name) { keepAlive(settings.audio.inputDevice.observe([](auto name) {
audio::set_input_device(name == "auto" ? "" : name); audio::set_input_device(name == "auto" ? "" : name);
}, true)); }));
project->loadProjectStartScript(); project->loadProjectStartScript();
if (!params.headless) { if (!params.headless) {

View file

@ -61,16 +61,19 @@ std::unique_ptr<UiDocument> UiDocument::read(
const scriptenv& penv, const scriptenv& penv,
const std::string& name, const std::string& name,
const io::path& file, const io::path& file,
const std::string& fileName const std::string& fileName,
scriptenv&& env
) { ) {
const std::string text = io::read_string(file); const std::string text = io::read_string(file);
auto xmldoc = xml::parse(file.string(), text); auto xmldoc = xml::parse(file.string(), text);
auto env = penv == nullptr if (env == nullptr) {
? scripting::create_doc_environment(scripting::get_root_environment(), name) env = penv == nullptr
: scripting::create_doc_environment(penv, name); ? scripting::create_doc_environment(scripting::get_root_environment(), name)
: scripting::create_doc_environment(penv, name);
}
gui::UiXmlReader reader(gui, scriptenv(env)); gui::UiXmlReader reader(gui, env);
auto view = reader.readXML(file.string(), *xmldoc->getRoot()); auto view = reader.readXML(file.string(), *xmldoc->getRoot());
view->setId("root"); view->setId("root");
UiDocScript script {}; UiDocScript script {};
@ -82,9 +85,3 @@ std::unique_ptr<UiDocument> UiDocument::read(
} }
return std::make_unique<UiDocument>(name, script, view, env); return std::make_unique<UiDocument>(name, script, view, env);
} }
std::shared_ptr<gui::UINode> UiDocument::readElement(
gui::GUI& gui, const io::path& file, const std::string& fileName
) {
return read(gui, nullptr, file.name(), file, fileName)->getRoot();
}

View file

@ -50,9 +50,7 @@ public:
const scriptenv& parent_env, const scriptenv& parent_env,
const std::string& name, const std::string& name,
const io::path& file, const io::path& file,
const std::string& fileName const std::string& fileName,
); scriptenv&& env = nullptr
static std::shared_ptr<gui::UINode> readElement(
gui::GUI&, const io::path& file, const std::string& fileName
); );
}; };

View file

@ -261,6 +261,7 @@ void LevelScreen::update(float delta) {
const Player& player = playerController->getPlayer(); const Player& player = playerController->getPlayer();
const Camera& camera = *player.currentCamera; const Camera& camera = *player.currentCamera;
decorator->update(paused ? 0.0f : delta, camera, weather); decorator->update(paused ? 0.0f : delta, camera, weather);
renderer->update(camera, delta * !hud->isPause());
} }
void LevelScreen::draw(float delta) { void LevelScreen::draw(float delta) {
@ -271,7 +272,6 @@ void LevelScreen::draw(float delta) {
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta()); scripting::on_entities_render(engine.getTime().getDelta());
} }
renderer->update(camera, delta * !hud->isPause());
renderer->renderFrame(ctx, camera, hudVisible, *postProcessing); renderer->renderFrame(ctx, camera, hudVisible, *postProcessing);
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_frontend_render(); scripting::on_frontend_render();

View file

@ -136,18 +136,18 @@ void Mesh::addBox(
const bool enabledSides[6], const bool enabledSides[6],
const glm::mat4& transform const glm::mat4& transform
) { ) {
if (enabledSides[0]) // north if (enabledSides[0]) // east
addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0], transform); addPlane(pos-X*size, Z*size, Y*size, -X, uvs[0], transform);
if (enabledSides[1]) // south if (enabledSides[1]) // west
addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1], transform); addPlane(pos+X*size, -Z*size, Y*size, X, uvs[1] * glm::vec2(-1, 1), transform);
if (enabledSides[2]) // top if (enabledSides[2]) // bottom
addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2] * glm::vec2(-1), transform); addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[2], transform);
if (enabledSides[3]) // bottom if (enabledSides[3]) // top
addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3] * glm::vec2(-1, 1), transform); addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[3] * glm::vec2(-1, 1), transform);
if (enabledSides[4]) // west if (enabledSides[4]) // south
addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4], transform); addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[4], transform);
if (enabledSides[5]) // east if (enabledSides[5]) // north
addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5] * glm::vec2(-1, 1), transform); addPlane(pos+Z*size, X*size, Y*size, Z, uvs[5] * glm::vec2(-1, 1), transform);
} }
void Mesh::scale(const glm::vec3& size) { void Mesh::scale(const glm::vec3& size) {

View file

@ -73,7 +73,7 @@ Mesh<VertexStructure>::~Mesh() {
MeshStats::meshesCount--; MeshStats::meshesCount--;
glDeleteVertexArrays(1, &vao); glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo); glDeleteBuffers(1, &vbo);
for (int i = ibos.size() - 1; i >= 0; i--) { for (int i = 0; i < ibos.size(); i++) {
glDeleteBuffers(1, &ibos[i].ibo); glDeleteBuffers(1, &ibos[i].ibo);
} }
} }
@ -98,7 +98,7 @@ void Mesh<VertexStructure>::reload(
glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STREAM_DRAW); glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STREAM_DRAW);
} }
for (int i = indices.size(); i < ibos.size(); i++) { for (int i = 0; i < ibos.size(); i++) {
glDeleteBuffers(1, &ibos[i].ibo); glDeleteBuffers(1, &ibos[i].ibo);
} }
ibos.clear(); ibos.clear();

View file

@ -271,8 +271,7 @@ void Decorator::updateBlockEmitters(const Camera& camera) {
remove = true; remove = true;
} }
} else { } else {
iter = blockEmitters.erase(iter); remove = true;
continue;
} }
if (util::distance2(iter->first, glm::ivec3(camera.position)) > if (util::distance2(iter->first, glm::ivec3(camera.position)) >
UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER) { UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER) {

View file

@ -48,6 +48,7 @@ static inline void update_particle(
void ParticlesRenderer::updateParticles(float delta) { void ParticlesRenderer::updateParticles(float delta) {
std::vector<const Texture*> unusedTextures; std::vector<const Texture*> unusedTextures;
visibleParticles = 0;
for (auto& [texture, vec] : particles) { for (auto& [texture, vec] : particles) {
if (vec.empty()) { if (vec.empty()) {
@ -186,7 +187,6 @@ void ParticlesRenderer::update(const Camera& camera, float delta) {
void ParticlesRenderer::render(const Camera& camera) { void ParticlesRenderer::render(const Camera& camera) {
aliveEmitters = emitters.size(); aliveEmitters = emitters.size();
visibleParticles = 0;
bool backlight = settings.backlight.get(); bool backlight = settings.backlight.get();

View file

@ -57,6 +57,10 @@ FullCheckBox::FullCheckBox(
add(checkbox); add(checkbox);
auto& checkboxActions = actions;
label->setMargin(glm::vec4(5.f, 5.f, 0.f, 0.f)); label->setMargin(glm::vec4(5.f, 5.f, 0.f, 0.f));
label->listenAction(UIAction::CLICK, [&checkboxActions](auto& gui) {
checkboxActions.notify(UIAction::CLICK, gui);
});
add(label); add(label);
} }

View file

@ -83,12 +83,12 @@ static util::TextureRegion determine_texture_region(
const Assets& assets, const std::string& texture const Assets& assets, const std::string& texture
) { ) {
static std::array<std::string, 6> faces { static std::array<std::string, 6> faces {
"blocks:dbg_north",
"blocks:dbg_south",
"blocks:dbg_top",
"blocks:dbg_bottom",
"blocks:dbg_west",
"blocks:dbg_east", "blocks:dbg_east",
"blocks:dbg_west",
"blocks:dbg_bottom",
"blocks:dbg_top",
"blocks:dbg_south",
"blocks:dbg_north",
}; };
if (texture.length() < 2 || texture.at(0) != '$') { if (texture.length() < 2 || texture.at(0) != '$') {

View file

@ -889,8 +889,8 @@ static std::shared_ptr<UINode> read_iframe(
return iframe; return iframe;
} }
UiXmlReader::UiXmlReader(gui::GUI& gui, scriptenv&& env) UiXmlReader::UiXmlReader(gui::GUI& gui, const scriptenv& env)
: gui(gui), env(std::move(env)) { : gui(gui), env(env) {
contextStack.emplace(""); contextStack.emplace("");
add("image", read_image); add("image", read_image);
add("canvas", read_canvas); add("canvas", read_canvas);
@ -922,8 +922,7 @@ void UiXmlReader::addIgnore(const std::string& tag) {
ignored.insert(tag); ignored.insert(tag);
} }
std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element) {
) {
if (element.has("if")) { if (element.has("if")) {
const auto& cond = element.attr("if").getText(); const auto& cond = element.attr("if").getText();
if (cond.empty() || cond == "false" || cond == "nil") return nullptr; if (cond.empty() || cond == "false" || cond == "nil") return nullptr;

View file

@ -20,9 +20,9 @@ namespace gui {
std::unordered_set<std::string> ignored; std::unordered_set<std::string> ignored;
std::stack<std::string> contextStack; std::stack<std::string> contextStack;
std::string filename; std::string filename;
scriptenv env; const scriptenv& env;
public: public:
UiXmlReader(gui::GUI& gui, scriptenv&& env); UiXmlReader(gui::GUI& gui, const scriptenv& env);
void add(const std::string& tag, uinode_reader reader); void add(const std::string& tag, uinode_reader reader);
bool hasReader(const std::string& tag) const; bool hasReader(const std::string& tag) const;

View file

@ -38,6 +38,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("audio"); builder.addSection("audio");
builder.add("enabled", &settings.audio.enabled, false); builder.add("enabled", &settings.audio.enabled, false);
builder.add("recording-enabled", &settings.audio.recordingEnabled);
builder.add("volume-master", &settings.audio.volumeMaster); builder.add("volume-master", &settings.audio.volumeMaster);
builder.add("volume-regular", &settings.audio.volumeRegular); builder.add("volume-regular", &settings.audio.volumeRegular);
builder.add("volume-ui", &settings.audio.volumeUI); builder.add("volume-ui", &settings.audio.volumeUI);
@ -101,6 +102,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("system"); builder.addSection("system");
builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders); builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders);
builder.add("preserve-assets-during-frame", &settings.system.preserveAssetsDuringFrame); builder.add("preserve-assets-during-frame", &settings.system.preserveAssetsDuringFrame);
builder.add("direct-scripting-data-access", &settings.system.directScriptingDataAccess);
} }
dv::value SettingsHandler::getValue(const std::string& name) const { dv::value SettingsHandler::getValue(const std::string& name) const {

View file

@ -55,7 +55,7 @@ void LightSolver::remove(int x, int y, int z) {
} }
void LightSolver::solve(Chunk* prevailingChunk) { void LightSolver::solve(Chunk* prevailingChunk) {
const int coords[] = { static const int coords[] = {
0, 0, 1, 0, 0, 1,
0, 0,-1, 0, 0,-1,
0, 1, 0, 0, 1, 0,

View file

@ -14,9 +14,8 @@
static debug::Logger logger("lighting"); static debug::Logger logger("lighting");
Lighting::Lighting(const Content& content, Chunks& chunks) Lighting::Lighting(const ContentIndices& indices, Chunks& chunks)
: content(content), chunks(chunks) { : indices(indices), chunks(chunks) {
auto& indices = *content.getIndices();
solverR = std::make_unique<LightSolver>(indices, chunks, 0); solverR = std::make_unique<LightSolver>(indices, chunks, 0);
solverG = std::make_unique<LightSolver>(indices, chunks, 1); solverG = std::make_unique<LightSolver>(indices, chunks, 1);
solverB = std::make_unique<LightSolver>(indices, chunks, 2); solverB = std::make_unique<LightSolver>(indices, chunks, 2);
@ -70,7 +69,7 @@ void Lighting::prebuildSkyLight(Chunk& chunk, const ContentIndices& indices) {
} }
void Lighting::buildSkyLight(int cx, int cz){ void Lighting::buildSkyLight(int cx, int cz){
const auto blockDefs = content.getIndices()->blocks.getDefs(); const auto blockDefs = indices.blocks.getDefs();
Chunk* chunk = chunks.getChunk(cx, cz); Chunk* chunk = chunks.getChunk(cx, cz);
if (chunk == nullptr) { if (chunk == nullptr) {
@ -110,7 +109,7 @@ void Lighting::onChunkLoaded(int cx, int cz, bool expand) {
auto& solverB = *this->solverB; auto& solverB = *this->solverB;
auto& solverS = *this->solverS; auto& solverS = *this->solverS;
auto blockDefs = content.getIndices()->blocks.getDefs(); auto blockDefs = indices.blocks.getDefs();
auto chunk = chunks.getChunk(cx, cz); auto chunk = chunks.getChunk(cx, cz);
if (chunk == nullptr) { if (chunk == nullptr) {
logger.error() << "attempted to build lights to chunk missing in local matrix"; logger.error() << "attempted to build lights to chunk missing in local matrix";
@ -174,69 +173,72 @@ void Lighting::onChunkLoaded(int cx, int cz, bool expand) {
} }
void Lighting::onBlockSet(int x, int y, int z, blockid_t id){ void Lighting::onBlockSet(int x, int y, int z, blockid_t id){
const auto& block = content.getIndices()->blocks.require(id); const auto& block = indices.blocks.require(id);
solverR->remove(x,y,z);
solverG->remove(x,y,z);
solverB->remove(x,y,z);
if (id == 0){ auto chunk = chunks.getChunkByVoxel(glm::ivec3{x, y, z});
auto chunk = chunks.getChunkByVoxel(glm::ivec3{x, y, z});
solverR->solve(chunk); if (block.skyLightPassing) {
solverG->solve(chunk); if (chunks.getLight(x, y + 1, z, 3) == 0xF) {
solverB->solve(chunk); for (int i = y; i >= 0; i--) {
if (chunks.getLight(x,y+1,z, 3) == 0xF){ voxel* vox = chunks.get(x, i, z);
for (int i = y; i >= 0; i--){ if (vox == nullptr ||
voxel* vox = chunks.get(x,i,z); indices.blocks.require(vox->id).skyLightPassing ==
if ((vox == nullptr || vox->id != 0) && block.skyLightPassing) false)
break; break;
solverS->add(x,i,z, 0xF); solverS->add(x, i, z, 0xF);
} }
} }
const int coords[] = {
0, 0, 1,
0, 0,-1,
0, 1, 0,
0,-1, 0,
1, 0, 0,
-1, 0, 0
};
for (int i = 0; i < 6; i++) {
int lx = x + coords[i * 3];
int ly = y + coords[i * 3 + 1];
int lz = z + coords[i * 3 + 2];
solverR->add(lx, ly, lz);
solverG->add(lx, ly, lz);
solverB->add(lx, ly, lz);
solverS->add(lx, ly, lz);
solverR->solve(chunk);
solverG->solve(chunk);
solverB->solve(chunk);
solverS->solve(chunk);
}
} else { } else {
auto chunk = chunks.getChunkByVoxel(glm::ivec3{x, y, z}); solverS->remove(x, y, z);
if (!block.skyLightPassing){ for (int i = y - 1; i >= 0; i--) {
solverS->remove(x,y,z); voxel* vox = chunks.get(x, i, z);
for (int i = y-1; i >= 0; i--){ if (vox == nullptr ||
solverS->remove(x,i,z); indices.blocks.require(vox->id).skyLightPassing == false) {
if (i == 0 || chunks.get(x,i-1,z)->id != 0){ break;
break;
}
} }
solverS->solve(chunk); solverS->remove(x, i, z);
}
solverR->solve(chunk);
solverG->solve(chunk);
solverB->solve(chunk);
if (block.emission[0] || block.emission[1] || block.emission[2]){
solverR->add(x,y,z,block.emission[0]);
solverG->add(x,y,z,block.emission[1]);
solverB->add(x,y,z,block.emission[2]);
solverR->solve(chunk);
solverG->solve(chunk);
solverB->solve(chunk);
} }
solverS->solve();
} }
solverR->remove(x, y, z);
solverG->remove(x, y, z);
solverB->remove(x, y, z);
solverR->solve(chunk);
solverG->solve(chunk);
solverB->solve(chunk);
static const int coords[] = {
0, 0, 1,
0, 0,-1,
0, 1, 0,
0,-1, 0,
1, 0, 0,
-1, 0, 0
};
for (int i = 0; i < 6; i++) {
int lx = x + coords[i * 3];
int ly = y + coords[i * 3 + 1];
int lz = z + coords[i * 3 + 2];
solverR->add(lx, ly, lz);
solverG->add(lx, ly, lz);
solverB->add(lx, ly, lz);
solverS->add(lx, ly, lz);
}
if (block.emission[0]) {
solverR->add(x, y, z, block.emission[0]);
}
if (block.emission[1]) {
solverG->add(x, y, z, block.emission[1]);
}
if (block.emission[2]) {
solverB->add(x, y, z, block.emission[2]);
}
solverR->solve(chunk);
solverG->solve(chunk);
solverB->solve(chunk);
solverS->solve(chunk);
} }

View file

@ -2,21 +2,20 @@
#include "typedefs.hpp" #include "typedefs.hpp"
class Content;
class ContentIndices; class ContentIndices;
class Chunk; class Chunk;
class Chunks; class Chunks;
class LightSolver; class LightSolver;
class Lighting { class Lighting {
const Content& content; const ContentIndices& indices;
Chunks& chunks; Chunks& chunks;
std::unique_ptr<LightSolver> solverR; std::unique_ptr<LightSolver> solverR;
std::unique_ptr<LightSolver> solverG; std::unique_ptr<LightSolver> solverG;
std::unique_ptr<LightSolver> solverB; std::unique_ptr<LightSolver> solverB;
std::unique_ptr<LightSolver> solverS; std::unique_ptr<LightSolver> solverS;
public: public:
Lighting(const Content& content, Chunks& chunks); Lighting(const ContentIndices& indices, Chunks& chunks);
~Lighting(); ~Lighting();
void clear(); void clear();

View file

@ -392,8 +392,11 @@ void EngineController::reconfigPacks(
auto& manager = contentControl.scan(); auto& manager = contentControl.scan();
reconfig_packs_inside(manager, names, packsToAdd, packsToRemove); reconfig_packs_inside(manager, names, packsToAdd, packsToRemove);
const auto& settings = engine.getSettings();
auto& wfile = *world->wfile; auto& wfile = *world->wfile;
wfile.removeIndices(packsToRemove); if (!settings.debug.generatorTestMode.get()) {
wfile.removeIndices(packsToRemove);
}
wfile.writePacks(manager.getAll(names)); wfile.writePacks(manager.getAll(names));
reopenWorld(world); reopenWorld(world);
} }

View file

@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include "content/Content.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp" #include "engine/EnginePaths.hpp"
@ -41,7 +42,7 @@ LevelController::LevelController(
if (clientPlayer) { if (clientPlayer) {
chunks->lighting = std::make_unique<Lighting>( chunks->lighting = std::make_unique<Lighting>(
level->content, *clientPlayer->chunks *level->content.getIndices(), *clientPlayer->chunks
); );
} }
blocks = std::make_unique<BlocksController>( blocks = std::make_unique<BlocksController>(

View file

@ -1116,28 +1116,34 @@ static int l_gui_load_document(lua::State* L) {
auto args = lua::tovalue(L, 3); auto args = lua::tovalue(L, 3);
auto prefix = filename.entryPoint(); auto prefix = filename.entryPoint();
auto env = scripting::get_root_environment(); auto parentEnv = scripting::get_root_environment();
if (content) { if (content) {
if (auto runtime = content->getPackRuntime(prefix)) { if (auto runtime = content->getPackRuntime(prefix)) {
env = runtime->getEnvironment(); parentEnv = runtime->getEnvironment();
} }
} }
auto documentPtr = UiDocument::read( auto env = scripting::create_doc_environment(parentEnv, alias);
engine->getGUI(), std::move(env), alias, filename, filename.string()
);
auto document = documentPtr.get();
engine->requireAssets().store(std::move(documentPtr), alias);
// namespace extension // namespace extension
if (lua::istable(L, 4)) { if (lua::istable(L, 4)) {
if (lua::get_from(L, "table", "merge")) { if (lua::get_from(L, "table", "extend")) {
lua::pushenv(L, *document->getEnvironment()); lua::pushenv(L, *env);
lua::pushvalue(L, 4); lua::pushvalue(L, 4);
lua::call(L, 2, 0); lua::call(L, 2, 0);
lua::pop(L); lua::pop(L);
} }
} }
auto documentPtr = UiDocument::read(
engine->getGUI(),
nullptr,
alias,
filename,
filename.string(),
std::move(env)
);
auto document = documentPtr.get();
engine->requireAssets().store(std::move(documentPtr), alias);
scripting::on_ui_open(document, {args}); scripting::on_ui_open(document, {args});
return 0; return 0;
} }

View file

@ -62,11 +62,20 @@ static int l_utc_offset(lua::State* L) {
return lua::pushnumber(L, offset); return lua::pushnumber(L, offset);
} }
static auto start_time = std::chrono::steady_clock::now();
static int l_precise_time(lua::State* L) {
auto time = std::chrono::steady_clock::now();
std::chrono::duration<double> delta = time - start_time;
return lua::pushnumber(L, delta.count());
}
const luaL_Reg timelib[] = { const luaL_Reg timelib[] = {
{"uptime", lua::wrap<l_uptime>}, {"uptime", lua::wrap<l_uptime>},
{"delta", lua::wrap<l_delta>}, {"delta", lua::wrap<l_delta>},
{"utc_time", lua::wrap<l_utc_time>}, {"utc_time", lua::wrap<l_utc_time>},
{"utc_offset", lua::wrap<l_utc_offset>}, {"utc_offset", lua::wrap<l_utc_offset>},
{"local_time", lua::wrap<l_local_time>}, {"local_time", lua::wrap<l_local_time>},
{"precise_time", lua::wrap<l_precise_time>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -17,6 +17,11 @@
#error LuaJIT required #error LuaJIT required
#endif #endif
#ifndef LUA_TCDATA
// unsafe, use for filtering only
#define LUA_TCDATA 10
#endif
namespace lua { namespace lua {
class luaerror : public std::runtime_error { class luaerror : public std::runtime_error {
public: public:

View file

@ -15,8 +15,12 @@
#include "usertypes/lua_type_pcmstream.hpp" #include "usertypes/lua_type_pcmstream.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
static debug::Logger logger("lua-state"); namespace {
static lua::State* main_thread = nullptr; debug::Logger logger("lua-state");
lua::State* main_thread = nullptr;
bool headless_mode = false;
const std::unordered_map<std::string, std::string>* project_args;
}
using namespace lua; using namespace lua;
@ -146,20 +150,53 @@ void lua::init_state(State* L, StateType stateType) {
newusertype<LuaHeightmap>(L); newusertype<LuaHeightmap>(L);
newusertype<LuaVoxelFragment>(L); newusertype<LuaVoxelFragment>(L);
newusertype<LuaCanvas>(L); newusertype<LuaCanvas>(L);
pushboolean(L, headless_mode);
setglobal(L, "__VC_HEADLESS");
createtable(L, 0, project_args->size());
for (const auto& [key, value] : *project_args) {
pushstring(L, value);
setfield(L, key);
}
setglobal(L, "__VC_PROJECT_ARGS");
auto file = "res:scripts/stdmin.lua";
auto src = io::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
newusertype<LuaRandom>(L);
if (getglobal(L, "random")) {
if (getglobal(L, "__vc_Random")) {
setfield(L, "Random");
}
pop(L);
}
newusertype<LuaPCMStream>(L);
if (getglobal(L, "audio")) {
if (getglobal(L, "__vc_PCMStream")) {
setfield(L, "PCMStream");
}
pop(L);
}
if (stateType == StateType::GENERATOR) {
pushnil(L);
setglobal(L, "ffi");
}
} }
void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
logger.info() << LUA_VERSION; logger.info() << LUA_VERSION;
logger.info() << LUAJIT_VERSION; logger.info() << LUAJIT_VERSION;
headless_mode = params.headless;
project_args = &params.projectArgs;
main_thread = create_state( main_thread = create_state(
paths, params.headless ? StateType::SCRIPT : StateType::BASE paths, params.headless ? StateType::SCRIPT : StateType::BASE
); );
lua::pushstring(main_thread, params.scriptFile.stem().u8string()); lua::pushstring(main_thread, params.scriptFile.stem().u8string());
lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); lua::setglobal(main_thread, "__VC_SCRIPT_NAME");
lua::pushboolean(main_thread, params.headless);
lua::setglobal(main_thread, "__VC_HEADLESS");
} }
void lua::finalize() { void lua::finalize() {
@ -191,29 +228,5 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
throw luaerror("could not initialize Lua state"); throw luaerror("could not initialize Lua state");
} }
init_state(L, stateType); init_state(L, stateType);
auto file = "res:scripts/stdmin.lua";
auto src = io::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
newusertype<LuaRandom>(L);
if (getglobal(L, "random")) {
if (getglobal(L, "__vc_Random")) {
setfield(L, "Random");
}
pop(L);
}
newusertype<LuaPCMStream>(L);
if (getglobal(L, "audio")) {
if (getglobal(L, "__vc_PCMStream")) {
setfield(L, "PCMStream");
}
pop(L);
}
if (stateType == StateType::GENERATOR) {
pushnil(L);
setglobal(L, "ffi");
}
return L; return L;
} }

View file

@ -5,6 +5,8 @@
#include <iostream> #include <iostream>
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "engine/Engine.hpp"
#include "debug/Logger.hpp"
using namespace lua; using namespace lua;
@ -12,6 +14,8 @@ static int next_environment = 1;
std::unordered_map<std::type_index, std::string> lua::usertypeNames; std::unordered_map<std::type_index, std::string> lua::usertypeNames;
static debug::Logger logger("lua-util");
int lua::userdata_destructor(lua::State* L) { int lua::userdata_destructor(lua::State* L) {
if (auto obj = touserdata<Userdata>(L, 1)) { if (auto obj = touserdata<Userdata>(L, 1)) {
obj->~Userdata(); obj->~Userdata();
@ -76,6 +80,10 @@ int lua::pushwstring(State* L, const std::wstring& str) {
return pushstring(L, util::wstr2str_utf8(str)); return pushstring(L, util::wstr2str_utf8(str));
} }
static std::string to_ptr_string(State* L, int idx) {
return std::to_string(reinterpret_cast<ptrdiff_t>(lua_topointer(L, idx)));
}
dv::value lua::tovalue(State* L, int idx) { dv::value lua::tovalue(State* L, int idx) {
using dv::value_type; using dv::value_type;
auto type = lua::type(L, idx); auto type = lua::type(L, idx);
@ -95,11 +103,16 @@ dv::value lua::tovalue(State* L, int idx) {
} }
} }
case LUA_TFUNCTION: case LUA_TFUNCTION:
return "<function " + return "<function " + to_ptr_string(L, idx) + ">";
std::to_string( case LUA_TTHREAD:
reinterpret_cast<ptrdiff_t>(lua_topointer(L, idx)) return "<thread " + to_ptr_string(L, idx) + ">";
) + case LUA_TUSERDATA:
">"; if (auto userdata = touserdata<Userdata>(L, idx)) {
return "<userdata:" + userdata->getTypeName() + " " + to_ptr_string(L, idx) + ">";
}
return "<userdata " + to_ptr_string(L, idx) + ">";
case LUA_TLIGHTUSERDATA:
return "<lightuserdata " + to_ptr_string(L, idx) + ">";
case LUA_TSTRING: case LUA_TSTRING:
return std::string(tostring(L, idx)); return std::string(tostring(L, idx));
case LUA_TTABLE: { case LUA_TTABLE: {
@ -369,3 +382,55 @@ void lua::remove_environment(State* L, int id) {
pushnil(L); pushnil(L);
store_env(L, id); store_env(L, id);
} }
static inline std::string_view bytearray_as_string_indirect(lua::State* L, int idx) {
requireglobal(L, "Bytearray_as_string");
pushvalue(L, -2);
call(L, 1, 1);
auto view = tolstring(L, -1);
pop(L, 2);
return view;
}
std::string_view lua::bytearray_as_string(lua::State* L, int idx) {
const auto& settings = scripting::engine->getSettings();
int luaType = type(L, idx);
if (luaType == LUA_TSTRING) {
return tolstring(L, idx);
} else if (luaType == LUA_TTABLE) {
return bytearray_as_string_indirect(L, idx);
}
pushvalue(L, idx);
if (settings.system.directScriptingDataAccess.get()) {
requireglobal(L, "Bytearray_as_ptr");
pushvalue(L, -2);
call(L, 1, 2);
auto view = tolstring(L, -2);
if (view == "0") {
if (luaType == LUA_TCDATA) {
logger.error() << "FFI-based Bytearray_as_ptr returned null-pointer";
}
pop(L, 2);
return bytearray_as_string_indirect(L, idx);
}
uint64_t size = touinteger(L, -1);
auto ptr = (const char*)std::stoull(std::string(view), nullptr, 16);
pop(L, 3);
return std::string_view(ptr, size);
} else {
return bytearray_as_string_indirect(L, idx);
}
}
void lua::loadbuffer(
lua::State* L, int env, const std::string& src, const std::string& file
) {
if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) {
throw luaerror(tostring(L, -1));
}
if (env && getregistry(L, ENVS_TABLE, env_name(env))) {
lua_setfenv(L, -2);
}
}

View file

@ -555,16 +555,9 @@ namespace lua {
return lua_setfenv(L, idx); return lua_setfenv(L, idx);
} }
inline void loadbuffer( void loadbuffer(
lua::State* L, int env, const std::string& src, const std::string& file lua::State* L, int env, const std::string& src, const std::string& file
) { );
if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) {
throw luaerror(tostring(L, -1));
}
if (env && getregistry(L, ENVS_TABLE, env_name(env))) {
lua_setfenv(L, -2);
}
}
inline void store_in( inline void store_in(
lua::State* L, const std::string& tableName, const std::string& name lua::State* L, const std::string& tableName, const std::string& name
@ -828,16 +821,5 @@ namespace lua {
return create_bytearray(L, bytes.data(), bytes.size()); return create_bytearray(L, bytes.data(), bytes.size());
} }
inline std::string_view bytearray_as_string(lua::State* L, int idx) { std::string_view bytearray_as_string(lua::State* L, int idx);
if (type(L, idx) == LUA_TSTRING) {
return tolstring(L, idx);
}
pushvalue(L, idx);
requireglobal(L, "Bytearray_as_string");
pushvalue(L, -2);
call(L, 1, 1);
auto view = tolstring(L, -1);
pop(L, 2);
return view;
}
} }

View file

@ -141,7 +141,7 @@ std::unique_ptr<IClientProjectScript> scripting::load_client_project_script(
auto source = io::read_string(script); auto source = io::read_string(script);
auto env = create_environment(nullptr); auto env = create_environment(nullptr);
lua::pushenv(L, *env); lua::pushenv(L, *env);
if (lua::getglobal(L, "__vc_app")) { if (lua::getregistry(L, "app")) {
lua::setfield(L, "app"); lua::setfield(L, "app");
} }
lua::pop(L); lua::pop(L);

View file

@ -10,6 +10,7 @@
struct AudioSettings { struct AudioSettings {
/// @brief try to initialize AL backend /// @brief try to initialize AL backend
FlagSetting enabled {true}; FlagSetting enabled {true};
FlagSetting recordingEnabled {false};
NumberSetting volumeMaster {1.0f, 0.0f, 1.0f, setting_format::percent}; NumberSetting volumeMaster {1.0f, 0.0f, 1.0f, setting_format::percent};
NumberSetting volumeRegular {1.0f, 0.0f, 1.0f, setting_format::percent}; NumberSetting volumeRegular {1.0f, 0.0f, 1.0f, setting_format::percent};
@ -126,6 +127,8 @@ struct SystemSettings {
IntegerSetting maxBgAssetLoaders {3, -4, 16}; IntegerSetting maxBgAssetLoaders {3, -4, 16};
/// @brief Preserve assets during current frame from being destroyed /// @brief Preserve assets during current frame from being destroyed
FlagSetting preserveAssetsDuringFrame {true}; FlagSetting preserveAssetsDuringFrame {true};
/// @brief Allow luajit cdata objects direct data access optimizations
FlagSetting directScriptingDataAccess {true};
}; };
struct EngineSettings { struct EngineSettings {

View file

@ -26,6 +26,13 @@ namespace util {
return last[0] == '-'; return last[0] == '-';
} }
bool isNextKeywordArg() const {
if (pos >= argc) {
throw std::runtime_error("unexpected end");
}
return argv[pos][0] == '-';
}
std::string next() { std::string next() {
if (pos >= argc) { if (pos >= argc) {
throw std::runtime_error("unexpected end"); throw std::runtime_error("unexpected end");

View file

@ -97,11 +97,32 @@ static bool perform_keyword(
throw std::runtime_error("unknown argument " + keyword); throw std::runtime_error("unknown argument " + keyword);
} }
static void parse_project_args(util::ArgsReader& reader, CoreParameters& params) {
while (reader.hasNext()) {
std::string key = reader.next();
if (!reader.isKeywordArg()) {
std::cerr << "--<keyword> argument expected" << std::endl;
return;
}
key = key.substr(2);
if (!reader.hasNext() || reader.isNextKeywordArg()) {
params.projectArgs[std::move(key)] = "";
continue;
}
std::string value = reader.next();
params.projectArgs[std::move(key)] = std::move(value);
}
}
bool parse_cmdline(int argc, char** argv, CoreParameters& params) { bool parse_cmdline(int argc, char** argv, CoreParameters& params) {
util::ArgsReader reader(argc, argv); util::ArgsReader reader(argc, argv);
reader.skip(); reader.skip();
while (reader.hasNext()) { while (reader.hasNext()) {
std::string token = reader.next(); std::string token = reader.next();
if (token == "--") {
parse_project_args(reader, params);
return true;
}
if (reader.isKeywordArg()) { if (reader.isKeywordArg()) {
if (!perform_keyword(reader, token, params)) { if (!perform_keyword(reader, token, params)) {
return false; return false;

View file

@ -3,6 +3,7 @@
#include <random> #include <random>
#include <string> #include <string>
#include <algorithm> #include <algorithm>
#include <functional>
namespace util { namespace util {
template < template <