diff --git a/.gitignore b/.gitignore
index 9056cce5..c0b0e1c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ Debug/voxel_engine
/export
/config
/out
+/projects
/misc
/world
@@ -47,4 +48,4 @@ appimage-build/
# libs
/libs/
-/vcpkg_installed/
\ No newline at end of file
+/vcpkg_installed/
diff --git a/doc/en/scripting.md b/doc/en/scripting.md
index 05a89a26..131b9cfa 100644
--- a/doc/en/scripting.md
+++ b/doc/en/scripting.md
@@ -10,6 +10,7 @@ Subsections:
- [Entities and components](scripting/ecs.md)
- [Libraries](#)
- [app](scripting/builtins/libapp.md)
+ - [assets](scripting/builtins/libassets.md)
- [base64](scripting/builtins/libbase64.md)
- [bjson, json, toml, yaml](scripting/filesystem.md)
- [block](scripting/builtins/libblock.md)
diff --git a/doc/en/scripting/builtins/libassets.md b/doc/en/scripting/builtins/libassets.md
new file mode 100644
index 00000000..bd095435
--- /dev/null
+++ b/doc/en/scripting/builtins/libassets.md
@@ -0,0 +1,28 @@
+# *assets* library
+
+A library for working with audio/visual assets.
+
+## Functions
+
+```lua
+-- Loads a texture
+assets.load_texture(
+ -- Array of bytes of an image file
+ data: table | Bytearray,
+ -- Texture name after loading
+ name: str,
+ -- Image file format (only png is supported)
+ [optional]
+ format: str = "png"
+)
+
+-- Parses and loads a 3D model
+assets.parse_model(
+ -- Model file format (xml / vcm)
+ format: str,
+ -- Contents of the model file
+ content: str,
+ -- Model name after loading
+ name: str
+)
+```
diff --git a/doc/en/scripting/builtins/libgui.md b/doc/en/scripting/builtins/libgui.md
index cf6839db..cbcca4ce 100644
--- a/doc/en/scripting/builtins/libgui.md
+++ b/doc/en/scripting/builtins/libgui.md
@@ -103,3 +103,9 @@ gui.load_document(
```
Loads a UI document with its script, returns the name of the document if successfully loaded.
+
+```lua
+gui.root: Document
+```
+
+Root UI document
diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md
index eaae3302..aa25e072 100644
--- a/doc/ru/scripting.md
+++ b/doc/ru/scripting.md
@@ -10,6 +10,7 @@
- [Сущности и компоненты](scripting/ecs.md)
- [Библиотеки](#)
- [app](scripting/builtins/libapp.md)
+ - [assets](scripting/builtins/libassets.md)
- [base64](scripting/builtins/libbase64.md)
- [bjson, json, toml, yaml](scripting/filesystem.md)
- [block](scripting/builtins/libblock.md)
diff --git a/doc/ru/scripting/builtins/libassets.md b/doc/ru/scripting/builtins/libassets.md
new file mode 100644
index 00000000..2c510948
--- /dev/null
+++ b/doc/ru/scripting/builtins/libassets.md
@@ -0,0 +1,28 @@
+# Библиотека *assets*
+
+Библиотека для работы с аудио/визуальными загружаемыми ресурсами.
+
+## Функции
+
+```lua
+-- Загружает текстуру
+assets.load_texture(
+ -- Массив байт файла изображения
+ data: table | Bytearray,
+ -- Имя текстуры после загрузки
+ name: str,
+ -- Формат файла изображения (поддерживается только png)
+ [опционально]
+ format: str = "png"
+)
+
+-- Парсит и загружает 3D модель
+assets.parse_model(
+ -- Формат файла модели (xml / vcm)
+ format: str,
+ -- Содержимое файла модели
+ content: str,
+ -- Имя модели после загрузки
+ name: str
+)
+```
diff --git a/doc/ru/scripting/builtins/libgui.md b/doc/ru/scripting/builtins/libgui.md
index aea2f44a..a9bffe48 100644
--- a/doc/ru/scripting/builtins/libgui.md
+++ b/doc/ru/scripting/builtins/libgui.md
@@ -100,3 +100,9 @@ gui.load_document(
```
Загружает UI документ с его скриптом, возвращает имя документа, если успешно загружен.
+
+```lua
+gui.root: Document
+```
+
+Корневой UI документ
diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua
index 00f6bdc6..9fc10f2a 100644
--- a/res/content/base/scripts/hud.lua
+++ b/res/content/base/scripts/hud.lua
@@ -21,6 +21,9 @@ function on_hud_open()
local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0})
local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE)
local drop = base_util.drop(ppos, itemid, 1, data, 1.5)
+ if not drop then
+ return
+ end
local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL))
drop.rigidbody:set_vel(velocity)
end)
diff --git a/res/layouts/code_editor.xml.lua b/res/layouts/code_editor.xml.lua
index de460f2e..4e6c68d4 100644
--- a/res/layouts/code_editor.xml.lua
+++ b/res/layouts/code_editor.xml.lua
@@ -81,6 +81,11 @@ local function refresh_file_title()
document.saveIcon.enabled = edited
document.title.text = gui.str('File')..' - '..current_file.filename
..(edited and ' *' or '')
+
+ local info = registry.get_info(current_file.filename)
+ if info and info.type == "model" then
+ pcall(run_current_file)
+ end
end
function on_control_combination(keycode)
@@ -118,7 +123,6 @@ function run_current_file()
local unit = info and info.unit
if script_type == "model" then
- print(current_file.filename)
clear_output()
local _, err = pcall(reload_model, current_file.filename, unit)
if err then
@@ -256,7 +260,7 @@ function open_file_in_editor(filename, line, mutable)
end
function on_open(mode)
- registry = require "core:internal/scripts_registry"
+ registry = __vc_scripts_registry
document.codePanel:setInterval(200, refresh_file_title)
diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua
index 94cbda8b..f7e47d9b 100644
--- a/res/layouts/console.xml.lua
+++ b/res/layouts/console.xml.lua
@@ -4,7 +4,7 @@ history = session.get_entry("commands_history")
history_pointer = #history
events.on("core:open_traceback", function()
- if modes then
+ if modes and modes.current ~= 'debug' then
modes:set('debug')
end
end)
diff --git a/res/layouts/files_panel.xml.lua b/res/layouts/files_panel.xml.lua
index a4d6ca39..30b0ddc8 100644
--- a/res/layouts/files_panel.xml.lua
+++ b/res/layouts/files_panel.xml.lua
@@ -43,11 +43,8 @@ function build_files_list(filenames, highlighted_part)
end
end
-function on_open(mode)
- registry = require "core:internal/scripts_registry"
-
- local files_list = document.filesList
-
+function on_open()
+ registry = __vc_scripts_registry
filenames = registry.filenames
table.sort(filenames)
build_files_list(filenames)
diff --git a/res/modules/internal/events.lua b/res/modules/internal/events.lua
new file mode 100644
index 00000000..4820ddd5
--- /dev/null
+++ b/res/modules/internal/events.lua
@@ -0,0 +1,48 @@
+local events = {
+ handlers = {}
+}
+
+function events.on(event, func)
+ if events.handlers[event] == nil then
+ events.handlers[event] = {}
+ end
+ table.insert(events.handlers[event], func)
+end
+
+function events.reset(event, func)
+ if func == nil then
+ events.handlers[event] = nil
+ else
+ events.handlers[event] = {func}
+ end
+end
+
+function events.remove_by_prefix(prefix)
+ for name, handlers in pairs(events.handlers) do
+ local actualname = name
+ if type(name) == 'table' then
+ actualname = name[1]
+ end
+ if actualname:sub(1, #prefix+1) == prefix..':' then
+ events.handlers[actualname] = nil
+ end
+ end
+end
+
+function events.emit(event, ...)
+ local result = nil
+ local handlers = events.handlers[event]
+ if handlers == nil then
+ return nil
+ end
+ for _, func in ipairs(handlers) do
+ local status, newres = xpcall(func, __vc__error, ...)
+ if not status then
+ debug.error("error in event ("..event..") handler: "..newres)
+ else
+ result = result or newres
+ end
+ end
+ return result
+end
+return events
diff --git a/res/modules/internal/maths_inline.lua b/res/modules/internal/maths_inline.lua
new file mode 100644
index 00000000..e3e37fcd
--- /dev/null
+++ b/res/modules/internal/maths_inline.lua
@@ -0,0 +1,212 @@
+-- =================================================== --
+-- ====================== vec3 ======================= --
+-- =================================================== --
+function vec3.add(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] + b[1]
+ dst[2] = a[2] + b[2]
+ dst[3] = a[3] + b[3]
+ else
+ dst[1] = a[1] + b
+ dst[2] = a[2] + b
+ dst[3] = a[3] + b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] + b[1], a[2] + b[2], a[3] + b[3]}
+ else
+ return {a[1] + b, a[2] + b, a[3] + b}
+ end
+ end
+end
+
+function vec3.sub(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] - b[1]
+ dst[2] = a[2] - b[2]
+ dst[3] = a[3] - b[3]
+ else
+ dst[1] = a[1] - b
+ dst[2] = a[2] - b
+ dst[3] = a[3] - b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] - b[1], a[2] - b[2], a[3] - b[3]}
+ else
+ return {a[1] - b, a[2] - b, a[3] - b}
+ end
+ end
+end
+
+function vec3.mul(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] * b[1]
+ dst[2] = a[2] * b[2]
+ dst[3] = a[3] * b[3]
+ else
+ dst[1] = a[1] * b
+ dst[2] = a[2] * b
+ dst[3] = a[3] * b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] * b[1], a[2] * b[2], a[3] * b[3]}
+ else
+ return {a[1] * b, a[2] * b, a[3] * b}
+ end
+ end
+end
+
+function vec3.div(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] / b[1]
+ dst[2] = a[2] / b[2]
+ dst[3] = a[3] / b[3]
+ else
+ dst[1] = a[1] / b
+ dst[2] = a[2] / b
+ dst[3] = a[3] / b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] / b[1], a[2] / b[2], a[3] / b[3]}
+ else
+ return {a[1] / b, a[2] / b, a[3] / b}
+ end
+ end
+end
+
+function vec3.abs(a, dst)
+ local x = a[1]
+ local y = a[2]
+ local z = a[3]
+ if dst then
+ dst[1] = x < 0.0 and -x or x
+ dst[2] = y < 0.0 and -y or y
+ dst[3] = z < 0.0 and -z or z
+ else
+ return {
+ x < 0.0 and -x or x,
+ y < 0.0 and -y or y,
+ z < 0.0 and -z or z,
+ }
+ end
+end
+
+function vec3.dot(a, b)
+ return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
+end
+
+-- =================================================== --
+-- ====================== vec2 ======================= --
+-- =================================================== --
+function vec2.add(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] + b[1]
+ dst[2] = a[2] + b[2]
+ else
+ dst[1] = a[1] + b
+ dst[2] = a[2] + b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] + b[1], a[2] + b[2]}
+ else
+ return {a[1] + b, a[2] + b}
+ end
+ end
+end
+
+function vec2.sub(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] - b[1]
+ dst[2] = a[2] - b[2]
+ else
+ dst[1] = a[1] - b
+ dst[2] = a[2] - b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] - b[1], a[2] - b[2]}
+ else
+ return {a[1] - b, a[2] - b}
+ end
+ end
+end
+
+function vec2.mul(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] * b[1]
+ dst[2] = a[2] * b[2]
+ else
+ dst[1] = a[1] * b
+ dst[2] = a[2] * b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] * b[1], a[2] * b[2]}
+ else
+ return {a[1] * b, a[2] * b}
+ end
+ end
+end
+
+function vec2.div(a, b, dst)
+ local btype = type(b)
+ if dst then
+ if btype == "table" then
+ dst[1] = a[1] / b[1]
+ dst[2] = a[2] / b[2]
+ else
+ dst[1] = a[1] / b
+ dst[2] = a[2] / b
+ end
+ return dst
+ else
+ if btype == "table" then
+ return {a[1] / b[1], a[2] / b[2]}
+ else
+ return {a[1] / b, a[2] / b}
+ end
+ end
+end
+
+function vec2.abs(a, dst)
+ local x = a[1]
+ local y = a[2]
+ if dst then
+ dst[1] = x < 0.0 and -x or x
+ dst[2] = y < 0.0 and -y or y
+ else
+ return {
+ x < 0.0 and -x or x,
+ y < 0.0 and -y or y,
+ }
+ end
+end
+
+function vec2.dot(a, b)
+ return a[1] * b[1] + a[2] * b[2]
+end
diff --git a/res/modules/internal/stream_providers/named_pipe_unix.lua b/res/modules/internal/stream_providers/named_pipe_unix.lua
index 0a9c8904..1843daba 100644
--- a/res/modules/internal/stream_providers/named_pipe_unix.lua
+++ b/res/modules/internal/stream_providers/named_pipe_unix.lua
@@ -22,16 +22,16 @@ local O_NONBLOCK = 0x800
local F_GETFL = 3
local function getError()
- local err = ffi.errno()
+ local err = FFI.errno()
- return ffi.string(C.strerror(err)).." ("..err..")"
+ return FFI.string(C.strerror(err)).." ("..err..")"
end
local lib = {}
function lib.read(fd, len)
local buffer = FFI.new("uint8_t[?]", len)
- local result = C.read(fd, buffer, len)
+ local result = tonumber(C.read(fd, buffer, len))
local out = Bytearray()
@@ -101,4 +101,4 @@ return function(path, mode)
end
return io_stream.new(fd, mode:find('b') ~= nil, lib)
-end
\ No newline at end of file
+end
diff --git a/res/modules/schedule.lua b/res/modules/schedule.lua
index 24d216f0..5d24a086 100644
--- a/res/modules/schedule.lua
+++ b/res/modules/schedule.lua
@@ -15,8 +15,9 @@ local Schedule = {
local timer = self._timer + dt
for id, interval in pairs(self._intervals) do
if timer - interval.last_called >= interval.delay then
- xpcall(interval.callback, function(s)
- debug.error(s..'\n'..debug.traceback())
+ local stack_size = debug.count_frames()
+ xpcall(interval.callback, function(msg)
+ __vc__error(msg, 1, 1, stack_size)
end)
interval.last_called = timer
local repetions = interval.repetions
diff --git a/res/project_client.lua b/res/project_client.lua
new file mode 100644
index 00000000..a1952c08
--- /dev/null
+++ b/res/project_client.lua
@@ -0,0 +1,26 @@
+local menubg
+
+function on_menu_clear()
+ if menubg then
+ menubg:destruct()
+ menubg = nil
+ end
+end
+
+function on_menu_setup()
+ local controller = {}
+ function controller.resize_menu_bg()
+ local w, h = unpack(gui.get_viewport())
+ if menubg then
+ menubg.region = {0, math.floor(h / 48), math.floor(w / 48), 0}
+ menubg.pos = {0, 0}
+ end
+ return w, h
+ end
+ gui.root.root:add(
+ "", controller)
+ menubg = gui.root.menubg
+ controller.resize_menu_bg()
+ menu.page = "main"
+end
diff --git a/res/scripts/post_content.lua b/res/scripts/post_content.lua
index 7b17876e..2ad3633b 100644
--- a/res/scripts/post_content.lua
+++ b/res/scripts/post_content.lua
@@ -62,5 +62,4 @@ end
cache_names(block)
cache_names(item)
-local scripts_registry = require "core:internal/scripts_registry"
-scripts_registry.build_registry()
+__vc_scripts_registry.build_registry()
diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua
index 6934fb1f..fa93d2c9 100644
--- a/res/scripts/stdlib.lua
+++ b/res/scripts/stdlib.lua
@@ -1,3 +1,5 @@
+local enable_experimental = core.get_setting("debug.enable-experimental")
+
------------------------------------------------
------ Extended kit of standard functions ------
------------------------------------------------
@@ -168,61 +170,16 @@ function inventory.set_description(invid, slot, description)
inventory.set_data(invid, slot, "description", description)
end
-------------------------------------------------
-------------------- Events ---------------------
-------------------------------------------------
-events = {
- handlers = {}
-}
-
-function events.on(event, func)
- if events.handlers[event] == nil then
- events.handlers[event] = {}
- end
- table.insert(events.handlers[event], func)
+if enable_experimental then
+ require "core:internal/maths_inline"
end
-function events.reset(event, func)
- if func == nil then
- events.handlers[event] = nil
- else
- events.handlers[event] = {func}
- end
-end
-
-function events.remove_by_prefix(prefix)
- for name, handlers in pairs(events.handlers) do
- local actualname = name
- if type(name) == 'table' then
- actualname = name[1]
- end
- if actualname:sub(1, #prefix+1) == prefix..':' then
- events.handlers[actualname] = nil
- end
- end
-end
+events = require "core:internal/events"
function pack.unload(prefix)
events.remove_by_prefix(prefix)
end
-function events.emit(event, ...)
- local result = nil
- local handlers = events.handlers[event]
- if handlers == nil then
- return nil
- end
- for _, func in ipairs(handlers) do
- local status, newres = xpcall(func, __vc__error, ...)
- if not status then
- debug.error("error in event ("..event..") handler: "..newres)
- else
- result = result or newres
- end
- end
- return result
-end
-
gui_util = require "core:internal/gui_util"
Document = gui_util.Document
@@ -237,6 +194,7 @@ end
_GUI_ROOT = Document.new("core:root")
_MENU = _GUI_ROOT.menu
menu = _MENU
+gui.root = _GUI_ROOT
--- Console library extension ---
console.cheats = {}
@@ -318,11 +276,12 @@ entities.get_all = function(uids)
end
local bytearray = require "core:internal/bytearray"
-
Bytearray = bytearray.FFIBytearray
Bytearray_as_string = bytearray.FFIBytearray_as_string
Bytearray_construct = function(...) return Bytearray(...) end
+__vc_scripts_registry = require "core:internal/scripts_registry"
+
file.open = require "core:internal/stream_providers/file"
file.open_named_pipe = require "core:internal/stream_providers/named_pipe"
@@ -341,6 +300,7 @@ else
end
ffi = nil
+__vc_lock_internal_modules()
math.randomseed(time.uptime() * 1536227939)
diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua
index 38fdbfdc..669d5874 100644
--- a/res/scripts/stdmin.lua
+++ b/res/scripts/stdmin.lua
@@ -550,6 +550,8 @@ function reload_module(name)
end
end
+local internal_locked = false
+
-- Load script with caching
--
-- path - script path `contentpack:filename`.
@@ -559,6 +561,11 @@ end
function __load_script(path, nocache)
local packname, filename = parse_path(path)
+ if internal_locked and (packname == "res" or packname == "core")
+ and filename:starts_with("modules/internal") then
+ error("access to core:internal modules outside of [core]")
+ end
+
-- __cached_scripts used in condition because cached result may be nil
if not nocache and __cached_scripts[path] ~= nil then
return package.loaded[path]
@@ -579,6 +586,10 @@ function __load_script(path, nocache)
return result
end
+function __vc_lock_internal_modules()
+ internal_locked = true
+end
+
function require(path)
if not string.find(path, ':') then
local prefix, _ = parse_path(_debug_getinfo(2).source)
diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp
index a12524c0..9b564787 100644
--- a/src/coders/vcm.cpp
+++ b/src/coders/vcm.cpp
@@ -174,7 +174,6 @@ std::unique_ptr vcm::parse(
"'model' tag expected as root, got '" + root.getTag() + "'"
);
}
- std::cout << xml::stringify(*doc) << std::endl;
return load_model(root);
} catch (const parsing_error& err) {
throw std::runtime_error(err.errorLog());
diff --git a/src/devtools/Project.cpp b/src/devtools/Project.cpp
index c1d382ee..881f6cc7 100644
--- a/src/devtools/Project.cpp
+++ b/src/devtools/Project.cpp
@@ -1,6 +1,9 @@
#include "Project.hpp"
#include "data/dv_util.hpp"
+#include "logic/scripting/scripting.hpp"
+
+Project::~Project() = default;
dv::value Project::serialize() const {
return dv::object({
diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp
index 857b58f1..be9d88a3 100644
--- a/src/devtools/Project.hpp
+++ b/src/devtools/Project.hpp
@@ -2,13 +2,21 @@
#include
#include
+#include
#include "interfaces/Serializable.hpp"
+namespace scripting {
+ class IClientProjectScript;
+}
+
struct Project : Serializable {
std::string name;
std::string title;
std::vector basePacks;
+ std::unique_ptr clientScript;
+
+ ~Project();
dv::value serialize() const override;
void deserialize(const dv::value& src) override;
diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp
index 6596733f..0288d609 100644
--- a/src/engine/Engine.cpp
+++ b/src/engine/Engine.cpp
@@ -60,6 +60,17 @@ static std::unique_ptr load_icon() {
return nullptr;
}
+static std::unique_ptr load_client_project_script() {
+ io::path scriptFile = "project:project_client.lua";
+ if (io::exists(scriptFile)) {
+ logger.info() << "starting project script";
+ return scripting::load_client_project_script(scriptFile);
+ } else {
+ logger.warning() << "project script does not exists";
+ }
+ return nullptr;
+}
+
Engine::Engine() = default;
Engine::~Engine() = default;
@@ -72,6 +83,68 @@ Engine& Engine::getInstance() {
return *instance;
}
+void Engine::onContentLoad() {
+ editor->loadTools();
+ langs::setup(langs::get_current(), paths.resPaths.collectRoots());
+
+ if (isHeadless()) {
+ return;
+ }
+ for (auto& pack : content->getAllContentPacks()) {
+ auto configFolder = pack.folder / "config";
+ auto bindsFile = configFolder / "bindings.toml";
+ if (io::is_regular_file(bindsFile)) {
+ input->getBindings().read(
+ toml::parse(
+ bindsFile.string(), io::read_string(bindsFile)
+ ),
+ BindType::BIND
+ );
+ }
+ }
+ loadAssets();
+}
+
+void Engine::initializeClient() {
+ std::string title = project->title;
+ if (title.empty()) {
+ title = "VoxelCore v" +
+ std::to_string(ENGINE_VERSION_MAJOR) + "." +
+ std::to_string(ENGINE_VERSION_MINOR);
+ }
+ if (ENGINE_DEBUG_BUILD) {
+ title += " [debug]";
+ }
+ auto [window, input] = Window::initialize(&settings.display, title);
+ if (!window || !input){
+ throw initialize_error("could not initialize window");
+ }
+ window->setFramerate(settings.display.framerate.get());
+
+ time.set(window->time());
+ if (auto icon = load_icon()) {
+ icon->flipY();
+ window->setIcon(icon.get());
+ }
+ this->window = std::move(window);
+ this->input = std::move(input);
+
+ loadControls();
+
+ gui = std::make_unique(*this);
+ if (ENGINE_DEBUG_BUILD) {
+ menus::create_version_label(*gui);
+ }
+ keepAlive(settings.display.fullscreen.observe(
+ [this](bool value) {
+ if (value != this->window->isFullscreen()) {
+ this->window->toggleFullscreen();
+ }
+ },
+ true
+ ));
+}
+
void Engine::initialize(CoreParameters coreParameters) {
params = std::move(coreParameters);
settingsHandler = std::make_unique(settings);
@@ -100,78 +173,28 @@ void Engine::initialize(CoreParameters coreParameters) {
controller = std::make_unique(*this);
if (!params.headless) {
- std::string title = project->title;
- if (title.empty()) {
- title = "VoxelCore v" +
- std::to_string(ENGINE_VERSION_MAJOR) + "." +
- std::to_string(ENGINE_VERSION_MINOR);
- }
- if (ENGINE_DEBUG_BUILD) {
- title += " [debug]";
- }
- auto [window, input] = Window::initialize(&settings.display, title);
- if (!window || !input){
- throw initialize_error("could not initialize window");
- }
- window->setFramerate(settings.display.framerate.get());
-
- time.set(window->time());
- if (auto icon = load_icon()) {
- icon->flipY();
- window->setIcon(icon.get());
- }
- this->window = std::move(window);
- this->input = std::move(input);
-
- loadControls();
-
- gui = std::make_unique(*this);
- if (ENGINE_DEBUG_BUILD) {
- menus::create_version_label(*gui);
- }
- keepAlive(settings.display.fullscreen.observe(
- [this](bool value) {
- if (value != this->window->isFullscreen()) {
- this->window->toggleFullscreen();
- }
- },
- true
- ));
+ initializeClient();
}
audio::initialize(!params.headless, settings.audio);
- bool langNotSet = settings.ui.language.get() == "auto";
- if (langNotSet) {
+ if (settings.ui.language.get() == "auto") {
settings.ui.language.set(
langs::locale_by_envlocale(platform::detect_locale())
);
}
content = std::make_unique(*project, paths, *input, [this]() {
- editor->loadTools();
- langs::setup(langs::get_current(), paths.resPaths.collectRoots());
- if (!isHeadless()) {
- for (auto& pack : content->getAllContentPacks()) {
- auto configFolder = pack.folder / "config";
- auto bindsFile = configFolder / "bindings.toml";
- if (io::is_regular_file(bindsFile)) {
- input->getBindings().read(
- toml::parse(
- bindsFile.string(), io::read_string(bindsFile)
- ),
- BindType::BIND
- );
- }
- }
- loadAssets();
- }
+ onContentLoad();
});
scripting::initialize(this);
+
if (!isHeadless()) {
gui->setPageLoader(scripting::create_page_loader());
}
keepAlive(settings.ui.language.observe([this](auto lang) {
langs::setup(lang, paths.resPaths.collectRoots());
}, true));
+
+ project->clientScript = load_client_project_script();
}
void Engine::loadSettings() {
@@ -286,6 +309,7 @@ void Engine::close() {
audio::close();
network.reset();
clearKeepedObjects();
+ project.reset();
scripting::close();
logger.info() << "scripting finished";
if (!params.headless) {
@@ -345,10 +369,19 @@ void Engine::loadProject() {
}
void Engine::setScreen(std::shared_ptr screen) {
+ if (project->clientScript && this->screen) {
+ project->clientScript->onScreenChange(this->screen->getName(), false);
+ }
// reset audio channels (stop all sources)
audio::reset_channel(audio::get_channel_index("regular"));
audio::reset_channel(audio::get_channel_index("ambient"));
this->screen = std::move(screen);
+ if (this->screen) {
+ this->screen->onOpen();
+ }
+ if (project->clientScript && this->screen) {
+ project->clientScript->onScreenChange(this->screen->getName(), true);
+ }
}
void Engine::onWorldOpen(std::unique_ptr level, int64_t localPlayer) {
diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp
index 8f8683da..98bcd497 100644
--- a/src/engine/Engine.hpp
+++ b/src/engine/Engine.hpp
@@ -82,6 +82,9 @@ class Engine : public util::ObjectsKeeper {
void updateHotkeys();
void loadAssets();
void loadProject();
+
+ void initializeClient();
+ void onContentLoad();
public:
Engine();
~Engine();
@@ -174,4 +177,8 @@ public:
devtools::Editor& getEditor() {
return *editor;
}
+
+ const Project& getProject() {
+ return *project;
+ }
};
diff --git a/src/engine/Mainloop.cpp b/src/engine/Mainloop.cpp
index ecfd17bc..a14c5464 100644
--- a/src/engine/Mainloop.cpp
+++ b/src/engine/Mainloop.cpp
@@ -2,10 +2,13 @@
#include "Engine.hpp"
#include "debug/Logger.hpp"
+#include "devtools/Project.hpp"
#include "frontend/screens/MenuScreen.hpp"
#include "frontend/screens/LevelScreen.hpp"
#include "window/Window.hpp"
#include "world/Level.hpp"
+#include "graphics/ui/GUI.hpp"
+#include "graphics/ui/elements/Container.hpp"
static debug::Logger logger("mainloop");
@@ -36,6 +39,7 @@ void Mainloop::run() {
while (!window.isShouldClose()){
time.update(window.time());
engine.updateFrontend();
+
if (!window.isIconified()) {
engine.renderFrame();
}
diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp
index 7c6f8d13..3e9a3a27 100644
--- a/src/frontend/UiDocument.cpp
+++ b/src/frontend/UiDocument.cpp
@@ -14,11 +14,13 @@ UiDocument::UiDocument(
const std::shared_ptr& root,
scriptenv env
) : id(std::move(id)), script(script), root(root), env(std::move(env)) {
- gui::UINode::getIndices(root, map);
+ rebuildIndices();
}
void UiDocument::rebuildIndices() {
+ map.clear();
gui::UINode::getIndices(root, map);
+ map["root"] = root;
}
const UINodesMap& UiDocument::getMap() const {
diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp
index 08dfcf40..39271224 100644
--- a/src/frontend/hud.cpp
+++ b/src/frontend/hud.cpp
@@ -324,7 +324,7 @@ void Hud::updateWorldGenDebug() {
void Hud::update(bool visible) {
const auto& chunks = *player.chunks;
- bool is_menu_open = menu.hasOpenPage();
+ bool isMenuOpen = menu.hasOpenPage();
debugPanel->setVisible(
debug && visible && !(inventoryOpen && inventoryView == nullptr)
@@ -333,13 +333,13 @@ void Hud::update(bool visible) {
if (!visible && inventoryOpen) {
closeInventory();
}
- if (pause && !is_menu_open) {
+ if (pause && !isMenuOpen) {
setPause(false);
}
if (!gui.isFocusCaught()) {
processInput(visible);
}
- if ((is_menu_open || inventoryOpen) == input.getCursor().locked) {
+ if ((isMenuOpen || inventoryOpen) == input.getCursor().locked) {
input.toggleCursor();
}
@@ -360,8 +360,8 @@ void Hud::update(bool visible) {
contentAccessPanel->setSize(glm::vec2(caSize.x, windowSize.y));
contentAccess->setMinSize(glm::vec2(1, windowSize.y));
hotbarView->setVisible(visible && !(secondUI && !inventoryView));
- darkOverlay->setVisible(is_menu_open);
- menu.setVisible(is_menu_open);
+ darkOverlay->setVisible(isMenuOpen);
+ menu.setVisible(isMenuOpen);
if (visible) {
for (auto& element : elements) {
diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp
index ca80ed11..aa007293 100644
--- a/src/frontend/screens/LevelScreen.cpp
+++ b/src/frontend/screens/LevelScreen.cpp
@@ -97,7 +97,6 @@ LevelScreen::LevelScreen(
animator->addAnimations(assets.getAnimations());
loadDecorations();
- initializeContent();
}
LevelScreen::~LevelScreen() {
@@ -112,6 +111,10 @@ LevelScreen::~LevelScreen() {
engine.getPaths().setCurrentWorldFolder("");
}
+void LevelScreen::onOpen() {
+ initializeContent();
+}
+
void LevelScreen::initializeContent() {
auto& content = controller->getLevel()->content;
for (auto& entry : content.getPacks()) {
diff --git a/src/frontend/screens/LevelScreen.hpp b/src/frontend/screens/LevelScreen.hpp
index b4f6c93a..616258e5 100644
--- a/src/frontend/screens/LevelScreen.hpp
+++ b/src/frontend/screens/LevelScreen.hpp
@@ -53,8 +53,13 @@ public:
);
~LevelScreen();
+ void onOpen() override;
void update(float delta) override;
void draw(float delta) override;
void onEngineShutdown() override;
+
+ const char* getName() const override {
+ return "level";
+ }
};
diff --git a/src/frontend/screens/MenuScreen.cpp b/src/frontend/screens/MenuScreen.cpp
index be114ed3..c1339494 100644
--- a/src/frontend/screens/MenuScreen.cpp
+++ b/src/frontend/screens/MenuScreen.cpp
@@ -13,12 +13,6 @@
#include "engine/Engine.hpp"
MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
- engine.getContentControl().resetContent();
-
- auto menu = engine.getGUI().getMenu();
- menu->reset();
- menu->setPage("main");
-
uicamera =
std::make_unique(glm::vec3(), engine.getWindow().getSize().y);
uicamera->perspective = false;
@@ -29,33 +23,17 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
MenuScreen::~MenuScreen() = default;
+void MenuScreen::onOpen() {
+ engine.getContentControl().resetContent();
+
+ auto menu = engine.getGUI().getMenu();
+ menu->reset();
+}
+
void MenuScreen::update(float delta) {
}
void MenuScreen::draw(float delta) {
- auto assets = engine.getAssets();
-
display::clear();
display::setBgColor(glm::vec3(0.2f));
-
- const auto& size = engine.getWindow().getSize();
- uint width = size.x;
- uint height = size.y;
-
- uicamera->setFov(height);
- uicamera->setAspectRatio(width / static_cast(height));
- auto uishader = assets->get("ui");
- uishader->use();
- uishader->uniformMatrix("u_projview", uicamera->getProjView());
-
- auto bg = assets->get("gui/menubg");
- batch->begin();
- batch->texture(bg);
- batch->rect(
- 0, 0,
- width, height, 0, 0, 0,
- UVRegion(0, 0, width / bg->getWidth(), height / bg->getHeight()),
- false, false, glm::vec4(1.0f)
- );
- batch->flush();
}
diff --git a/src/frontend/screens/MenuScreen.hpp b/src/frontend/screens/MenuScreen.hpp
index f9b5dd48..a8caf0fe 100644
--- a/src/frontend/screens/MenuScreen.hpp
+++ b/src/frontend/screens/MenuScreen.hpp
@@ -13,6 +13,12 @@ public:
MenuScreen(Engine& engine);
~MenuScreen();
+ void onOpen() override;
+
void update(float delta) override;
void draw(float delta) override;
+
+ const char* getName() const override {
+ return "menu";
+ }
};
diff --git a/src/frontend/screens/Screen.hpp b/src/frontend/screens/Screen.hpp
index dde2f555..c24dc8b3 100644
--- a/src/frontend/screens/Screen.hpp
+++ b/src/frontend/screens/Screen.hpp
@@ -13,7 +13,9 @@ protected:
public:
Screen(Engine& engine);
virtual ~Screen();
+ virtual void onOpen() = 0;
virtual void update(float delta) = 0;
virtual void draw(float delta) = 0;
virtual void onEngineShutdown() {};
+ virtual const char* getName() const = 0;
};
diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp
index ceb2f4a0..3ab438b1 100644
--- a/src/graphics/ui/GUI.cpp
+++ b/src/graphics/ui/GUI.cpp
@@ -56,6 +56,13 @@ GUI::GUI(Engine& engine)
store("tooltip", tooltip);
store("tooltip.label", UINode::find(tooltip, "tooltip.label"));
container->add(tooltip);
+
+ rootDocument = std::make_unique(
+ "core:root",
+ uidocscript {},
+ std::dynamic_pointer_cast(container),
+ nullptr
+ );
}
GUI::~GUI() = default;
@@ -74,15 +81,8 @@ std::shared_ptr