diff --git a/doc/en/scripting/builtins/libgui.md b/doc/en/scripting/builtins/libgui.md index e3c109566..53d6e4ad2 100644 --- a/doc/en/scripting/builtins/libgui.md +++ b/doc/en/scripting/builtins/libgui.md @@ -149,16 +149,22 @@ gui.load_document( args: table ) -> str --- Loads a template into the layout +-- Loads and processes layout template from file gui.template( -- template name in /layouts/templates without path and extension name: str, -- variable table (can be used in markup) -- * Ex: -- * text in this case is the value from params with the text key - params: table, - -- table, available in events as the global variable DATA - [optional] data: table + params: table +) -> str + +-- Processes layout template from string +gui.process_template( + -- template source code + source: str, + -- variable table (same as gui.template) + params: table ) -> str ``` diff --git a/doc/en/scripting/builtins/libtime.md b/doc/en/scripting/builtins/libtime.md index 8fcd4035f..51e125e9c 100644 --- a/doc/en/scripting/builtins/libtime.md +++ b/doc/en/scripting/builtins/libtime.md @@ -1,13 +1,21 @@ -# *time* library +# *time* Library -```python -time.uptime() -> float +```lua +-- 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. diff --git a/doc/ru/scripting/builtins/libgui.md b/doc/ru/scripting/builtins/libgui.md index a744639ea..9c2eb7418 100644 --- a/doc/ru/scripting/builtins/libgui.md +++ b/doc/ru/scripting/builtins/libgui.md @@ -147,17 +147,23 @@ gui.load_document( args: table -- параметры для события on_open ) -> string --- Загружает шаблон в лояут +-- Обрабатывает xml шаблон макета из файла gui.template( -- имя шаблона в /layouts/templates без пути и расширения name: string, -- таблица переменных (может быть использована в разметке) -- * Пр: -- * text в данном случае, это значение из params по ключу text - params: table, - -- таблица, доступная в событиях как глобальная переменная DATA - [опционально] data: table + params: table ) -> string + +-- Обрабатывает xml шаблон макета из строки +gui.process_template( + -- шаблон в виде строки + source: string, + -- таблица переменных, как в gui.template + params: table +) ``` ## Корневой документ diff --git a/doc/ru/scripting/builtins/libtime.md b/doc/ru/scripting/builtins/libtime.md index 81bae58f7..62e95892a 100644 --- a/doc/ru/scripting/builtins/libtime.md +++ b/doc/ru/scripting/builtins/libtime.md @@ -15,4 +15,7 @@ time.local_time() -> int -- Возвращает смещение локального времени от UTC в секундах time.utc_offset() -> int + +-- Возвращает время высокой точности в секундах. За точку отсчёта берётся запуск движка. +time.precise_time() -> number ``` diff --git a/res/devtools/syntax/lua.toml b/res/devtools/syntax/lua.toml index d2469d2fb..45e697bbe 100644 --- a/res/devtools/syntax/lua.toml +++ b/res/devtools/syntax/lua.toml @@ -8,5 +8,5 @@ multiline-string-end = "]]" keywords = [ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", - "until", "while", "self", "error" + "until", "while", "self", "error", "require" ] diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index 1ff217534..475ccc51b 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -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) postfix = postfix or "" document.root:add(gui.template("track_setting", { @@ -10,10 +10,10 @@ function create_setting(id, name, step, postfix) step=step, postfix=postfix })) - update_setting(app.get_setting(id), id, name, postfix) + update_trackbar_label(app.get_setting(id), id, name, postfix) end -function update_setting(x, id, name, postfix) +function update_trackbar_label(x, id, name, postfix) app.set_setting(id, x) -- updating label document[id..".L"].text = string.format( @@ -24,12 +24,22 @@ function update_setting(x, id, name, postfix) ) 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) tooltip = tooltip or '' document.root:add(string.format( - "%s", + "%s", id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") )) + update_checkbox_setting(id, app.get_setting(id)) end local initialized = false @@ -55,11 +65,12 @@ function on_open() prev_amplitude = amplitude end) end - create_setting("audio.volume-master", "Master Volume", 0.01) - create_setting("audio.volume-regular", "Regular Sounds", 0.01) - create_setting("audio.volume-ui", "UI Sounds", 0.01) - create_setting("audio.volume-ambient", "Ambient", 0.01) - create_setting("audio.volume-music", "Music", 0.01) + create_trackbar_setting("audio.volume-master", "Master Volume", 0.01) + create_trackbar_setting("audio.volume-regular", "Regular Sounds", 0.01) + create_trackbar_setting("audio.volume-ui", "UI Sounds", 0.01) + create_trackbar_setting("audio.volume-ambient", "Ambient", 0.01) + create_trackbar_setting("audio.volume-music", "Music", 0.01) + document.root:add("") document.root:add("".. @@ -70,5 +70,5 @@ function on_open() create_checkbox("camera.inertia", "Camera Inertia") create_checkbox("camera.fov-effects", "Camera FOV Effects") create_checkbox("display.limit-fps-iconified", "Limit Background FPS") - create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip") + create_trackbar_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip") end diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 806f48d0c..6a4596a04 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -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) postfix = postfix or "" tooltip = tooltip or "" @@ -14,10 +14,10 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease) tooltip=tooltip, changeonrelease=changeonrelease })) - update_setting(app.get_setting(id), id, name, postfix) + update_trackbar_label(app.get_setting(id), id, name, postfix) end -function update_setting(x, id, name, postfix) +function update_trackbar_label(x, id, name, postfix) -- updating label document[id..".L"].text = string.format( "%s: %s%s", @@ -36,15 +36,15 @@ function create_checkbox(id, name, tooltip) end function on_open() - create_setting("chunks.load-distance", "Load Distance", 1) - create_setting("chunks.load-speed", "Load Speed", 1) - create_setting("graphics.fog-curve", "Fog Curve", 0.1) + create_trackbar_setting("chunks.load-distance", "Load Distance", 1) + create_trackbar_setting("chunks.load-speed", "Load Speed", 1) + create_trackbar_setting("graphics.fog-curve", "Fog Curve", 0.1) create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip") create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip") - create_setting("graphics.ssao", "SSAO", 1, "", "graphics.ssao.tooltip") - create_setting("graphics.shadows-quality", "Shadows quality", 1) - create_setting("graphics.clouds-quality", "Clouds quality", 1) + create_trackbar_setting("graphics.ssao", "SSAO", 1, "", "graphics.ssao.tooltip") + create_trackbar_setting("graphics.shadows-quality", "Shadows quality", 1) + create_trackbar_setting("graphics.clouds-quality", "Clouds quality", 1) end diff --git a/res/layouts/templates/track_setting.xml b/res/layouts/templates/track_setting.xml index f01b8f842..79cdb41c1 100644 --- a/res/layouts/templates/track_setting.xml +++ b/res/layouts/templates/track_setting.xml @@ -3,5 +3,5 @@ + sub-consumer='function(x) update_trackbar_label(x, "%{id}", "%{name}", "%{postfix}") end'/> diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index c7c756f1c..739bb7cea 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -215,6 +215,13 @@ FFIBytearray = { } 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 t = type(bytes) if t == "cdata" then @@ -283,6 +290,7 @@ local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4) return { FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), FFIBytearray_as_string = FFIBytearray_as_string, + FFIBytearray_as_ptr = FFIBytearray_as_ptr, FFIU16view = FFIU16view, FFII16view = FFII16view, FFIU32view = FFIU32view, diff --git a/res/modules/internal/console.lua b/res/modules/internal/console.lua new file mode 100644 index 000000000..5b4f6b69c --- /dev/null +++ b/res/modules/internal/console.lua @@ -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 diff --git a/res/modules/internal/extensions/table.lua b/res/modules/internal/extensions/table.lua index a1856d383..d2d665755 100644 --- a/res/modules/internal/extensions/table.lua +++ b/res/modules/internal/extensions/table.lua @@ -57,6 +57,18 @@ function table.merge(t1, t2) return t1 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) for i, v in pairs(t) do t[i] = func(i, v) diff --git a/res/modules/internal/lifetime_events.lua b/res/modules/internal/lifetime_events.lua new file mode 100644 index 000000000..e1460c22c --- /dev/null +++ b/res/modules/internal/lifetime_events.lua @@ -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 +} diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index 779ad50bf..c2b102675 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -133,6 +133,9 @@ console.add_command( "Fill specified zone with blocks", function(args, kwargs) 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) for y=y1,y2 do for z=z1,z2 do diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 0e72ad86d..093501d75 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -12,7 +12,7 @@ function sleep(timesec) end end -function tb_frame_tostring(frame) +local function tb_frame_tostring(frame) local s = frame.short_src if frame.what ~= "C" then s = s .. ":" .. tostring(frame.currentline) @@ -26,7 +26,6 @@ function tb_frame_tostring(frame) end local __vc__app_script_coroutine -local __vc__is_post_runnable = false local function complete_app_lib(app) local __app_load_content = app.load_content @@ -46,7 +45,7 @@ local function complete_app_lib(app) app.tick = __app_tick local function call_in_app_script_co(func, ...) - if __vc__is_post_runnable then + if __vc_is_post_runnable_context() then func(...) return end @@ -192,6 +191,22 @@ function start_coroutine(chunk, name) __vc_named_coroutines[name] = co 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) debug.log("starting application script "..path) @@ -257,71 +272,8 @@ do end end ---- 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 - -function gui.template(name, params) - local text = file.read(file.find("layouts/templates/"..name..".xml")) - text = text:gsub("%%{([^}]+)}", function(n) +function gui.process_template(source, params) + local text = source:gsub("%%{([^}]+)}", function(n) local s = params[n] if s == nil then return @@ -341,6 +293,11 @@ function gui.template(name, params) return text 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" stdcomp = require "core:internal/stdcomp" entities.get = stdcomp.get_Entity @@ -365,7 +322,7 @@ if ffi.os == "Windows" then ffi.cdef[[ unsigned long GetCurrentProcessId(); ]] - + os.pid = ffi.C.GetCurrentProcessId() else ffi.cdef[[ @@ -378,236 +335,20 @@ end math.randomseed(time.uptime() * 1536227939) 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 = {} -local RULES_FILE = "world:rules.toml" -function __vc_on_world_open() - time.schedules.world = ScheduleGroup() +table.merge(_G, require "core:internal/lifetime_events") - 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 -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 - 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 +------------------------------------------ +---- Unsafe functions cleanup section ---- +------------------------------------------ - 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 = { "getregistry", "getupvalue", "setupvalue", "upvalueid", "upvaluejoin", "sethook", "gethook", "getinfo" @@ -626,9 +367,9 @@ debug.getinfo = function(lvl, fields) return debuginfo end -require "core:internal/deprecated" - ffi = nil __vc_app = nil __vc_lock_internal_modules() __vc_lock_internal_modules = nil +__vc_update_coroutines = nil +__VC_SCRIPT_NAME = "" diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 70e27f561..2868dd634 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,13 +1,18 @@ local _vc_headless = __VC_HEADLESS +local _vc_project_args = __VC_PROJECT_ARGS __VC_HEADLESS = nil +__VC_PROJECT_ARGS = nil vc = { is_headless = function() return _vc_headless end, - is_client = function () + is_client = function() 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 @@ -297,9 +302,14 @@ require "core:internal/extensions/file" require "core:internal/extensions/table" require "core:internal/extensions/string" +vc.get_project_arg = function (name) + return _vc_project_args[name] +end + local bytearray = require "core:internal/bytearray" Bytearray = bytearray.FFIBytearray Bytearray_as_string = bytearray.FFIBytearray_as_string +Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr U16view = bytearray.FFIU16view I16view = bytearray.FFII16view U32view = bytearray.FFIU32view diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index a6c9c5391..ff44ec754 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -18,6 +18,7 @@ devtools.output=Output # Tooltips 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.backlight.tooltip=Backlight to prevent total darkness graphics.dense-render.tooltip=Enables transparency in blocks like leaves diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 66dc40a42..ed3bb9686 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -39,6 +39,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/ # Подсказки audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой +audio.recording-enabled.tooltip=Включает функции записи звука graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья @@ -114,6 +115,7 @@ settings.Microphone=Микрофон settings.microphone.None=Нет settings.Clouds quality=Качество облаков settings.Acoustic effects=Акустические эффекты +settings.Microphone access=Доступ к микрофону # Управление chunks.reload=Перезагрузить Чанки diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 527fc7c71..d681d15a2 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -711,7 +711,7 @@ std::unique_ptr ALAudio::create(const AudioSettings& settings) { return nullptr; } bool effects = true; - ALint attribs[4] {}; + [[maybe_unused]] ALint attribs[4] {}; if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) { logger.info() << "AL effects extension present"; attribs[0] = ALC_MAX_AUXILIARY_SENDS; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index cfc84ce00..d6e32d089 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -190,18 +190,34 @@ void audio::initialize( audio::get_channel(channel.name)->setVolume(value * value); }, true)); } - objects_keeper.keepAlive(settings.acousticEffects.observe([=](auto value) { + objects_keeper.keepAlive(settings.acousticEffects.observe([=](bool value) { if (value) return; backend->setAcoustics(audio::Acoustics {}); }, true)); - if (inputEnabled) { - ::input_device = backend->openInputDevice("", 44100, 1, 16); - ::input_enabled = true; - } - if (::input_device) { - ::input_device->startCapture(); - } + ::input_enabled = inputEnabled; + + objects_keeper.keepAlive(settings.recordingEnabled.observe([&settings](bool enabled) { + if (!::input_enabled) { + return; + } + 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() { diff --git a/src/coders/commons.hpp b/src/coders/commons.hpp index 990474990..d374cd92b 100644 --- a/src/coders/commons.hpp +++ b/src/coders/commons.hpp @@ -21,6 +21,10 @@ inline bool is_identifier_part(int 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) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '.'; diff --git a/src/coders/syntax_parser.cpp b/src/coders/syntax_parser.cpp index 871cd3146..b0196ec2d 100644 --- a/src/coders/syntax_parser.cpp +++ b/src/coders/syntax_parser.cpp @@ -93,7 +93,7 @@ public: throw error("identifier expected"); } int start = pos; - while (hasNext() && is_identifier_part(source[pos])) { + while (hasNext() && is_minimal_identifier_part(source[pos])) { pos++; } return std::wstring(source.substr(start, pos - start)); diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 308fb8462..3083e1d36 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -55,12 +55,12 @@ model::Model VcmModel::squashed() const { } static const std::unordered_map side_indices { - {"north", 0}, - {"south", 1}, - {"top", 2}, - {"bottom", 3}, - {"west", 4}, - {"east", 5}, + {"east", 0}, + {"west", 1}, + {"bottom", 2}, + {"top", 3}, + {"south", 4}, + {"north", 5}, }; static bool to_boolean(const xml::Attribute& attr) { diff --git a/src/engine/CoreParameters.hpp b/src/engine/CoreParameters.hpp index b482f2bb5..b9f585f47 100644 --- a/src/engine/CoreParameters.hpp +++ b/src/engine/CoreParameters.hpp @@ -2,6 +2,7 @@ #include #include +#include struct CoreParameters { bool headless = false; @@ -13,4 +14,5 @@ struct CoreParameters { std::filesystem::path projectFolder; std::string debugServerString; int tps = 20; + std::unordered_map projectArgs; }; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 89a345ff6..879d95f0b 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -189,7 +189,7 @@ void Engine::initialize(CoreParameters coreParameters) { keepAlive(settings.audio.inputDevice.observe([](auto name) { audio::set_input_device(name == "auto" ? "" : name); - }, true)); + })); project->loadProjectStartScript(); if (!params.headless) { diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index a202c0d96..7e0c9bfc6 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -61,16 +61,19 @@ std::unique_ptr UiDocument::read( const scriptenv& penv, const std::string& name, const io::path& file, - const std::string& fileName + const std::string& fileName, + scriptenv&& env ) { const std::string text = io::read_string(file); auto xmldoc = xml::parse(file.string(), text); - auto env = penv == nullptr - ? scripting::create_doc_environment(scripting::get_root_environment(), name) - : scripting::create_doc_environment(penv, name); + if (env == nullptr) { + env = penv == nullptr + ? 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()); view->setId("root"); UiDocScript script {}; @@ -82,9 +85,3 @@ std::unique_ptr UiDocument::read( } return std::make_unique(name, script, view, env); } - -std::shared_ptr UiDocument::readElement( - gui::GUI& gui, const io::path& file, const std::string& fileName -) { - return read(gui, nullptr, file.name(), file, fileName)->getRoot(); -} diff --git a/src/frontend/UiDocument.hpp b/src/frontend/UiDocument.hpp index 57077bec0..5b40ca8ca 100644 --- a/src/frontend/UiDocument.hpp +++ b/src/frontend/UiDocument.hpp @@ -50,9 +50,7 @@ public: const scriptenv& parent_env, const std::string& name, const io::path& file, - const std::string& fileName - ); - static std::shared_ptr readElement( - gui::GUI&, const io::path& file, const std::string& fileName + const std::string& fileName, + scriptenv&& env = nullptr ); }; diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 452fc3111..33b637531 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -261,6 +261,7 @@ void LevelScreen::update(float delta) { const Player& player = playerController->getPlayer(); const Camera& camera = *player.currentCamera; decorator->update(paused ? 0.0f : delta, camera, weather); + renderer->update(camera, delta * !hud->isPause()); } void LevelScreen::draw(float delta) { @@ -271,7 +272,6 @@ void LevelScreen::draw(float delta) { if (!hud->isPause()) { scripting::on_entities_render(engine.getTime().getDelta()); } - renderer->update(camera, delta * !hud->isPause()); renderer->renderFrame(ctx, camera, hudVisible, *postProcessing); if (!hud->isPause()) { scripting::on_frontend_render(); diff --git a/src/graphics/commons/Model.cpp b/src/graphics/commons/Model.cpp index 2de10ad30..8c2194775 100644 --- a/src/graphics/commons/Model.cpp +++ b/src/graphics/commons/Model.cpp @@ -136,18 +136,18 @@ void Mesh::addBox( const bool enabledSides[6], const glm::mat4& transform ) { - if (enabledSides[0]) // north - addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0], transform); - if (enabledSides[1]) // south - addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1], transform); - if (enabledSides[2]) // top - addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2] * glm::vec2(-1), transform); - if (enabledSides[3]) // bottom - addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3] * glm::vec2(-1, 1), transform); - if (enabledSides[4]) // west - addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4], transform); - if (enabledSides[5]) // east - addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5] * glm::vec2(-1, 1), transform); + if (enabledSides[0]) // east + addPlane(pos-X*size, Z*size, Y*size, -X, uvs[0], transform); + if (enabledSides[1]) // west + addPlane(pos+X*size, -Z*size, Y*size, X, uvs[1] * glm::vec2(-1, 1), transform); + if (enabledSides[2]) // bottom + addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[2], transform); + if (enabledSides[3]) // top + addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[3] * glm::vec2(-1, 1), transform); + if (enabledSides[4]) // south + addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[4], transform); + if (enabledSides[5]) // north + addPlane(pos+Z*size, X*size, Y*size, Z, uvs[5] * glm::vec2(-1, 1), transform); } void Mesh::scale(const glm::vec3& size) { diff --git a/src/graphics/core/Mesh.inl b/src/graphics/core/Mesh.inl index c4cec9669..913da2bce 100644 --- a/src/graphics/core/Mesh.inl +++ b/src/graphics/core/Mesh.inl @@ -73,7 +73,7 @@ Mesh::~Mesh() { MeshStats::meshesCount--; glDeleteVertexArrays(1, &vao); 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); } } @@ -98,7 +98,7 @@ void Mesh::reload( 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); } ibos.clear(); diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 59840770a..b988898ce 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -271,8 +271,7 @@ void Decorator::updateBlockEmitters(const Camera& camera) { remove = true; } } else { - iter = blockEmitters.erase(iter); - continue; + remove = true; } if (util::distance2(iter->first, glm::ivec3(camera.position)) > UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER) { diff --git a/src/graphics/render/ParticlesRenderer.cpp b/src/graphics/render/ParticlesRenderer.cpp index 3c00830dd..d588cd63a 100644 --- a/src/graphics/render/ParticlesRenderer.cpp +++ b/src/graphics/render/ParticlesRenderer.cpp @@ -48,6 +48,7 @@ static inline void update_particle( void ParticlesRenderer::updateParticles(float delta) { std::vector unusedTextures; + visibleParticles = 0; for (auto& [texture, vec] : particles) { if (vec.empty()) { @@ -186,7 +187,6 @@ void ParticlesRenderer::update(const Camera& camera, float delta) { void ParticlesRenderer::render(const Camera& camera) { aliveEmitters = emitters.size(); - visibleParticles = 0; bool backlight = settings.backlight.get(); diff --git a/src/graphics/ui/elements/CheckBox.cpp b/src/graphics/ui/elements/CheckBox.cpp index 8694dc085..fe3a342dd 100644 --- a/src/graphics/ui/elements/CheckBox.cpp +++ b/src/graphics/ui/elements/CheckBox.cpp @@ -57,6 +57,10 @@ FullCheckBox::FullCheckBox( add(checkbox); + auto& checkboxActions = actions; 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); } diff --git a/src/graphics/ui/elements/ModelViewer.cpp b/src/graphics/ui/elements/ModelViewer.cpp index c1d736d71..727110f8b 100644 --- a/src/graphics/ui/elements/ModelViewer.cpp +++ b/src/graphics/ui/elements/ModelViewer.cpp @@ -83,12 +83,12 @@ static util::TextureRegion determine_texture_region( const Assets& assets, const std::string& texture ) { static std::array faces { - "blocks:dbg_north", - "blocks:dbg_south", - "blocks:dbg_top", - "blocks:dbg_bottom", - "blocks:dbg_west", "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) != '$') { diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index 33d57aa9a..e91b4553f 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -889,8 +889,8 @@ static std::shared_ptr read_iframe( return iframe; } -UiXmlReader::UiXmlReader(gui::GUI& gui, scriptenv&& env) - : gui(gui), env(std::move(env)) { +UiXmlReader::UiXmlReader(gui::GUI& gui, const scriptenv& env) + : gui(gui), env(env) { contextStack.emplace(""); add("image", read_image); add("canvas", read_canvas); @@ -922,8 +922,7 @@ void UiXmlReader::addIgnore(const std::string& tag) { ignored.insert(tag); } -std::shared_ptr UiXmlReader::readUINode(const xml::xmlelement& element -) { +std::shared_ptr UiXmlReader::readUINode(const xml::xmlelement& element) { if (element.has("if")) { const auto& cond = element.attr("if").getText(); if (cond.empty() || cond == "false" || cond == "nil") return nullptr; diff --git a/src/graphics/ui/gui_xml.hpp b/src/graphics/ui/gui_xml.hpp index 4a8a301ff..733503526 100644 --- a/src/graphics/ui/gui_xml.hpp +++ b/src/graphics/ui/gui_xml.hpp @@ -20,9 +20,9 @@ namespace gui { std::unordered_set ignored; std::stack contextStack; std::string filename; - scriptenv env; + const scriptenv& env; public: - UiXmlReader(gui::GUI& gui, scriptenv&& env); + UiXmlReader(gui::GUI& gui, const scriptenv& env); void add(const std::string& tag, uinode_reader reader); bool hasReader(const std::string& tag) const; diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index c66b5c451..8e8b0a2dc 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -38,6 +38,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.addSection("audio"); builder.add("enabled", &settings.audio.enabled, false); + builder.add("recording-enabled", &settings.audio.recordingEnabled); builder.add("volume-master", &settings.audio.volumeMaster); builder.add("volume-regular", &settings.audio.volumeRegular); builder.add("volume-ui", &settings.audio.volumeUI); @@ -101,6 +102,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.addSection("system"); builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders); 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 { diff --git a/src/lighting/LightSolver.cpp b/src/lighting/LightSolver.cpp index f43b4d487..a8d97424a 100644 --- a/src/lighting/LightSolver.cpp +++ b/src/lighting/LightSolver.cpp @@ -55,7 +55,7 @@ void LightSolver::remove(int x, int y, int z) { } void LightSolver::solve(Chunk* prevailingChunk) { - const int coords[] = { + static const int coords[] = { 0, 0, 1, 0, 0,-1, 0, 1, 0, diff --git a/src/lighting/Lighting.cpp b/src/lighting/Lighting.cpp index b17b6f333..c7f3445a5 100644 --- a/src/lighting/Lighting.cpp +++ b/src/lighting/Lighting.cpp @@ -14,9 +14,8 @@ static debug::Logger logger("lighting"); -Lighting::Lighting(const Content& content, Chunks& chunks) - : content(content), chunks(chunks) { - auto& indices = *content.getIndices(); +Lighting::Lighting(const ContentIndices& indices, Chunks& chunks) + : indices(indices), chunks(chunks) { solverR = std::make_unique(indices, chunks, 0); solverG = std::make_unique(indices, chunks, 1); solverB = std::make_unique(indices, chunks, 2); @@ -70,7 +69,7 @@ void Lighting::prebuildSkyLight(Chunk& chunk, const ContentIndices& indices) { } 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); if (chunk == nullptr) { @@ -110,7 +109,7 @@ void Lighting::onChunkLoaded(int cx, int cz, bool expand) { auto& solverB = *this->solverB; auto& solverS = *this->solverS; - auto blockDefs = content.getIndices()->blocks.getDefs(); + auto blockDefs = indices.blocks.getDefs(); auto chunk = chunks.getChunk(cx, cz); if (chunk == nullptr) { 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){ - const auto& block = content.getIndices()->blocks.require(id); - solverR->remove(x,y,z); - solverG->remove(x,y,z); - solverB->remove(x,y,z); + const auto& block = indices.blocks.require(id); - if (id == 0){ - auto chunk = chunks.getChunkByVoxel(glm::ivec3{x, y, z}); - solverR->solve(chunk); - solverG->solve(chunk); - solverB->solve(chunk); - if (chunks.getLight(x,y+1,z, 3) == 0xF){ - for (int i = y; i >= 0; i--){ - voxel* vox = chunks.get(x,i,z); - if ((vox == nullptr || vox->id != 0) && block.skyLightPassing) + auto chunk = chunks.getChunkByVoxel(glm::ivec3{x, y, z}); + + if (block.skyLightPassing) { + if (chunks.getLight(x, y + 1, z, 3) == 0xF) { + for (int i = y; i >= 0; i--) { + voxel* vox = chunks.get(x, i, z); + if (vox == nullptr || + indices.blocks.require(vox->id).skyLightPassing == + false) 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 { - auto chunk = chunks.getChunkByVoxel(glm::ivec3{x, y, z}); - if (!block.skyLightPassing){ - solverS->remove(x,y,z); - for (int i = y-1; i >= 0; i--){ - solverS->remove(x,i,z); - if (i == 0 || chunks.get(x,i-1,z)->id != 0){ - break; - } + solverS->remove(x, y, z); + for (int i = y - 1; i >= 0; i--) { + voxel* vox = chunks.get(x, i, z); + if (vox == nullptr || + indices.blocks.require(vox->id).skyLightPassing == false) { + break; } - solverS->solve(chunk); - } - 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->remove(x, i, z); } + 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); } diff --git a/src/lighting/Lighting.hpp b/src/lighting/Lighting.hpp index 622027f05..b5aec6d8f 100644 --- a/src/lighting/Lighting.hpp +++ b/src/lighting/Lighting.hpp @@ -2,21 +2,20 @@ #include "typedefs.hpp" -class Content; class ContentIndices; class Chunk; class Chunks; class LightSolver; class Lighting { - const Content& content; + const ContentIndices& indices; Chunks& chunks; std::unique_ptr solverR; std::unique_ptr solverG; std::unique_ptr solverB; std::unique_ptr solverS; public: - Lighting(const Content& content, Chunks& chunks); + Lighting(const ContentIndices& indices, Chunks& chunks); ~Lighting(); void clear(); diff --git a/src/logic/EngineController.cpp b/src/logic/EngineController.cpp index 7637b250e..ec9ef0b2a 100644 --- a/src/logic/EngineController.cpp +++ b/src/logic/EngineController.cpp @@ -392,8 +392,11 @@ void EngineController::reconfigPacks( auto& manager = contentControl.scan(); reconfig_packs_inside(manager, names, packsToAdd, packsToRemove); + const auto& settings = engine.getSettings(); auto& wfile = *world->wfile; - wfile.removeIndices(packsToRemove); + if (!settings.debug.generatorTestMode.get()) { + wfile.removeIndices(packsToRemove); + } wfile.writePacks(manager.getAll(names)); reopenWorld(world); } diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index 6f992ba4c..1c40b20df 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -2,6 +2,7 @@ #include +#include "content/Content.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" @@ -41,7 +42,7 @@ LevelController::LevelController( if (clientPlayer) { chunks->lighting = std::make_unique( - level->content, *clientPlayer->chunks + *level->content.getIndices(), *clientPlayer->chunks ); } blocks = std::make_unique( diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 58a2f2be1..cdd3dad28 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1116,28 +1116,34 @@ static int l_gui_load_document(lua::State* L) { auto args = lua::tovalue(L, 3); auto prefix = filename.entryPoint(); - auto env = scripting::get_root_environment(); + auto parentEnv = scripting::get_root_environment(); if (content) { if (auto runtime = content->getPackRuntime(prefix)) { - env = runtime->getEnvironment(); + parentEnv = runtime->getEnvironment(); } } - auto documentPtr = UiDocument::read( - engine->getGUI(), std::move(env), alias, filename, filename.string() - ); - auto document = documentPtr.get(); - engine->requireAssets().store(std::move(documentPtr), alias); - + auto env = scripting::create_doc_environment(parentEnv, alias); // namespace extension if (lua::istable(L, 4)) { - if (lua::get_from(L, "table", "merge")) { - lua::pushenv(L, *document->getEnvironment()); + if (lua::get_from(L, "table", "extend")) { + lua::pushenv(L, *env); lua::pushvalue(L, 4); lua::call(L, 2, 0); 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}); return 0; } diff --git a/src/logic/scripting/lua/libs/libtime.cpp b/src/logic/scripting/lua/libs/libtime.cpp index f953955be..342226035 100644 --- a/src/logic/scripting/lua/libs/libtime.cpp +++ b/src/logic/scripting/lua/libs/libtime.cpp @@ -62,11 +62,20 @@ static int l_utc_offset(lua::State* L) { 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 delta = time - start_time; + return lua::pushnumber(L, delta.count()); +} + const luaL_Reg timelib[] = { {"uptime", lua::wrap}, {"delta", lua::wrap}, {"utc_time", lua::wrap}, {"utc_offset", lua::wrap}, {"local_time", lua::wrap}, + {"precise_time", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/lua_commons.hpp b/src/logic/scripting/lua/lua_commons.hpp index bb3163c86..8e77cbbc0 100644 --- a/src/logic/scripting/lua/lua_commons.hpp +++ b/src/logic/scripting/lua/lua_commons.hpp @@ -17,6 +17,11 @@ #error LuaJIT required #endif +#ifndef LUA_TCDATA +// unsafe, use for filtering only +#define LUA_TCDATA 10 +#endif + namespace lua { class luaerror : public std::runtime_error { public: diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index bd2f0359b..afb2f42eb 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -15,8 +15,12 @@ #include "usertypes/lua_type_pcmstream.hpp" #include "engine/Engine.hpp" -static debug::Logger logger("lua-state"); -static lua::State* main_thread = nullptr; +namespace { + debug::Logger logger("lua-state"); + lua::State* main_thread = nullptr; + bool headless_mode = false; + const std::unordered_map* project_args; +} using namespace lua; @@ -146,20 +150,53 @@ void lua::init_state(State* L, StateType stateType) { newusertype(L); newusertype(L); newusertype(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(L); + if (getglobal(L, "random")) { + if (getglobal(L, "__vc_Random")) { + setfield(L, "Random"); + } + pop(L); + } + newusertype(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) { logger.info() << LUA_VERSION; logger.info() << LUAJIT_VERSION; + headless_mode = params.headless; + project_args = ¶ms.projectArgs; main_thread = create_state( paths, params.headless ? StateType::SCRIPT : StateType::BASE ); lua::pushstring(main_thread, params.scriptFile.stem().u8string()); lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); - - lua::pushboolean(main_thread, params.headless); - lua::setglobal(main_thread, "__VC_HEADLESS"); } void lua::finalize() { @@ -191,29 +228,5 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) { throw luaerror("could not initialize Lua state"); } 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(L); - if (getglobal(L, "random")) { - if (getglobal(L, "__vc_Random")) { - setfield(L, "Random"); - } - pop(L); - } - newusertype(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; } diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index ded9cc79b..6457911b0 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -5,6 +5,8 @@ #include #include "util/stringutil.hpp" +#include "engine/Engine.hpp" +#include "debug/Logger.hpp" using namespace lua; @@ -12,6 +14,8 @@ static int next_environment = 1; std::unordered_map lua::usertypeNames; +static debug::Logger logger("lua-util"); + int lua::userdata_destructor(lua::State* L) { if (auto obj = touserdata(L, 1)) { obj->~Userdata(); @@ -76,6 +80,10 @@ int lua::pushwstring(State* L, const std::wstring& str) { return pushstring(L, util::wstr2str_utf8(str)); } +static std::string to_ptr_string(State* L, int idx) { + return std::to_string(reinterpret_cast(lua_topointer(L, idx))); +} + dv::value lua::tovalue(State* L, int idx) { using dv::value_type; auto type = lua::type(L, idx); @@ -95,11 +103,16 @@ dv::value lua::tovalue(State* L, int idx) { } } case LUA_TFUNCTION: - return "(lua_topointer(L, idx)) - ) + - ">"; + return ""; + case LUA_TTHREAD: + return ""; + case LUA_TUSERDATA: + if (auto userdata = touserdata(L, idx)) { + return "getTypeName() + " " + to_ptr_string(L, idx) + ">"; + } + return ""; + case LUA_TLIGHTUSERDATA: + return ""; case LUA_TSTRING: return std::string(tostring(L, idx)); case LUA_TTABLE: { @@ -369,3 +382,55 @@ void lua::remove_environment(State* L, int id) { pushnil(L); 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); + } +} diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index a7881bef7..d78afaad1 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -555,16 +555,9 @@ namespace lua { return lua_setfenv(L, idx); } - inline void loadbuffer( + void 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); - } - } + ); inline void store_in( 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()); } - inline 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; - } + std::string_view bytearray_as_string(lua::State* L, int idx); } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 806f92c24..37597b058 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -141,7 +141,7 @@ std::unique_ptr scripting::load_client_project_script( auto source = io::read_string(script); auto env = create_environment(nullptr); lua::pushenv(L, *env); - if (lua::getglobal(L, "__vc_app")) { + if (lua::getregistry(L, "app")) { lua::setfield(L, "app"); } lua::pop(L); diff --git a/src/settings.hpp b/src/settings.hpp index 782330f14..5220ca328 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -10,6 +10,7 @@ struct AudioSettings { /// @brief try to initialize AL backend FlagSetting enabled {true}; + FlagSetting recordingEnabled {false}; NumberSetting volumeMaster {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}; /// @brief Preserve assets during current frame from being destroyed FlagSetting preserveAssetsDuringFrame {true}; + /// @brief Allow luajit cdata objects direct data access optimizations + FlagSetting directScriptingDataAccess {true}; }; struct EngineSettings { diff --git a/src/util/ArgsReader.hpp b/src/util/ArgsReader.hpp index 4c8453766..c13c1dd3e 100644 --- a/src/util/ArgsReader.hpp +++ b/src/util/ArgsReader.hpp @@ -26,6 +26,13 @@ namespace util { return last[0] == '-'; } + bool isNextKeywordArg() const { + if (pos >= argc) { + throw std::runtime_error("unexpected end"); + } + return argv[pos][0] == '-'; + } + std::string next() { if (pos >= argc) { throw std::runtime_error("unexpected end"); diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index e41a0ea57..7f34bcea4 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -97,11 +97,32 @@ static bool perform_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 << "-- 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) { util::ArgsReader reader(argc, argv); reader.skip(); while (reader.hasNext()) { std::string token = reader.next(); + if (token == "--") { + parse_project_args(reader, params); + return true; + } if (reader.isKeywordArg()) { if (!perform_keyword(reader, token, params)) { return false; diff --git a/src/util/random.hpp b/src/util/random.hpp index 075d5aa72..d6405dfac 100644 --- a/src/util/random.hpp +++ b/src/util/random.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace util { template <