Merge branch 'dev' into pathfinding
This commit is contained in:
commit
ca1b761c8c
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,6 +9,7 @@ Debug/voxel_engine
|
||||
/export
|
||||
/config
|
||||
/out
|
||||
/projects
|
||||
|
||||
/misc
|
||||
/world
|
||||
@ -47,4 +48,4 @@ appimage-build/
|
||||
|
||||
# libs
|
||||
/libs/
|
||||
/vcpkg_installed/
|
||||
/vcpkg_installed/
|
||||
|
||||
@ -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)
|
||||
|
||||
28
doc/en/scripting/builtins/libassets.md
Normal file
28
doc/en/scripting/builtins/libassets.md
Normal file
@ -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
|
||||
)
|
||||
```
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
28
doc/ru/scripting/builtins/libassets.md
Normal file
28
doc/ru/scripting/builtins/libassets.md
Normal file
@ -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
|
||||
)
|
||||
```
|
||||
@ -100,3 +100,9 @@ gui.load_document(
|
||||
```
|
||||
|
||||
Загружает UI документ с его скриптом, возвращает имя документа, если успешно загружен.
|
||||
|
||||
```lua
|
||||
gui.root: Document
|
||||
```
|
||||
|
||||
Корневой UI документ
|
||||
|
||||
69
flake.nix
69
flake.nix
@ -1,16 +1,69 @@
|
||||
{
|
||||
description = "VoxelCore – voxel game engine in C++";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system: {
|
||||
devShells.default = with nixpkgs.legacyPackages.${system}; mkShell {
|
||||
nativeBuildInputs = [ cmake pkg-config ];
|
||||
buildInputs = [ glm glfw glew zlib libpng libvorbis openal luajit curl ]; # libglvnd
|
||||
packages = [ glfw mesa freeglut entt ];
|
||||
LD_LIBRARY_PATH = "${wayland}/lib:$LD_LIBRARY_PATH";
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
voxel-core = pkgs.stdenv.mkDerivation {
|
||||
name = "voxel-core";
|
||||
|
||||
src = ./.;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cmake
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
glm
|
||||
glfw
|
||||
glew
|
||||
zlib
|
||||
libpng
|
||||
libvorbis
|
||||
openal
|
||||
luajit
|
||||
curl
|
||||
entt
|
||||
mesa
|
||||
freeglut
|
||||
]; # libglvnd
|
||||
|
||||
packages = with pkgs; [
|
||||
glfw
|
||||
mesa
|
||||
freeglut
|
||||
entt
|
||||
];
|
||||
cmakeFlags = [
|
||||
"-DCMAKE_PREFIX_PATH=${pkgs.entt}"
|
||||
"-DCMAKE_INCLUDE_PATH=${pkgs.entt}/include"
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp VoxelEngine $out/bin/
|
||||
'';
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
packages.default = voxel-core;
|
||||
apps.default = {
|
||||
type = "app";
|
||||
program = "${voxel-core}/bin/VoxelCore";
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
48
res/modules/internal/events.lua
Normal file
48
res/modules/internal/events.lua
Normal file
@ -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
|
||||
212
res/modules/internal/maths_inline.lua
Normal file
212
res/modules/internal/maths_inline.lua
Normal file
@ -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
|
||||
@ -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
|
||||
end
|
||||
|
||||
@ -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
|
||||
|
||||
26
res/project_client.lua
Normal file
26
res/project_client.lua
Normal file
@ -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(
|
||||
"<image id='menubg' src='gui/menubg' size-func='DATA.resize_menu_bg' "..
|
||||
"z-index='-1' interactive='true'/>", controller)
|
||||
menubg = gui.root.menubg
|
||||
controller.resize_menu_bg()
|
||||
menu.page = "main"
|
||||
end
|
||||
@ -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()
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
local enable_experimental = core.get_setting("debug.enable-experimental")
|
||||
|
||||
------------------------------------------------
|
||||
------ Extended kit of standard functions ------
|
||||
------------------------------------------------
|
||||
@ -36,7 +38,6 @@ local function complete_app_lib(app)
|
||||
app.set_setting = core.set_setting
|
||||
app.tick = function()
|
||||
coroutine.yield()
|
||||
network.__process_events()
|
||||
end
|
||||
app.get_version = core.get_version
|
||||
app.get_setting_info = core.get_setting_info
|
||||
@ -169,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
|
||||
@ -238,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 = {}
|
||||
@ -319,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"
|
||||
|
||||
@ -342,6 +300,7 @@ else
|
||||
end
|
||||
|
||||
ffi = nil
|
||||
__vc_lock_internal_modules()
|
||||
|
||||
math.randomseed(time.uptime() * 1536227939)
|
||||
|
||||
@ -612,6 +571,8 @@ function __process_post_runnables()
|
||||
for _, name in ipairs(dead) do
|
||||
__vc_named_coroutines[name] = nil
|
||||
end
|
||||
|
||||
network.__process_events()
|
||||
end
|
||||
|
||||
function time.post_runnable(runnable)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -174,7 +174,6 @@ std::unique_ptr<model::Model> 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());
|
||||
|
||||
@ -416,19 +416,20 @@ void ContentLoader::load() {
|
||||
|
||||
template <class T>
|
||||
static void load_script(const Content& content, T& def) {
|
||||
const auto& name = def.name;
|
||||
size_t pos = name.find(':');
|
||||
const auto& scriptName = def.scriptFile;
|
||||
if (scriptName.empty()) return;
|
||||
size_t pos = scriptName.find(':');
|
||||
if (pos == std::string::npos) {
|
||||
throw std::runtime_error("invalid content unit name");
|
||||
}
|
||||
const auto runtime = content.getPackRuntime(name.substr(0, pos));
|
||||
const auto runtime = content.getPackRuntime(scriptName.substr(0, pos));
|
||||
const auto& pack = runtime->getInfo();
|
||||
const auto& folder = pack.folder;
|
||||
auto scriptfile = folder / ("scripts/" + def.scriptName + ".lua");
|
||||
if (io::is_regular_file(scriptfile)) {
|
||||
scripting::load_content_script(
|
||||
runtime->getEnvironment(),
|
||||
name,
|
||||
def.name,
|
||||
scriptfile,
|
||||
def.scriptFile,
|
||||
def.rt.funcsset
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -2,13 +2,21 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
namespace scripting {
|
||||
class IClientProjectScript;
|
||||
}
|
||||
|
||||
struct Project : Serializable {
|
||||
std::string name;
|
||||
std::string title;
|
||||
std::vector<std::string> basePacks;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
|
||||
~Project();
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
@ -60,6 +60,17 @@ static std::unique_ptr<ImageData> load_icon() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::unique_ptr<scripting::IClientProjectScript> load_client_project_script() {
|
||||
io::path scriptFile = "project:project_client.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project script";
|
||||
return scripting::load_client_project_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project script does not exists";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Engine::Engine() = default;
|
||||
Engine::~Engine() = default;
|
||||
|
||||
@ -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<gui::GUI>(*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<SettingsHandler>(settings);
|
||||
@ -100,78 +173,28 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
|
||||
controller = std::make_unique<EngineController>(*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<gui::GUI>(*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<ContentControl>(*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> 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> level, int64_t localPlayer) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -14,11 +14,13 @@ UiDocument::UiDocument(
|
||||
const std::shared_ptr<gui::UINode>& 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 {
|
||||
|
||||
@ -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) {
|
||||
@ -538,6 +538,7 @@ void Hud::closeInventory() {
|
||||
exchangeSlotInv = nullptr;
|
||||
inventoryOpen = false;
|
||||
inventoryView = nullptr;
|
||||
secondInvView = nullptr;
|
||||
secondUI = nullptr;
|
||||
|
||||
for (auto& element : elements) {
|
||||
@ -597,6 +598,9 @@ void Hud::remove(const std::shared_ptr<UINode>& node) {
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
if (node == secondUI) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::setDebug(bool flag) {
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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";
|
||||
}
|
||||
};
|
||||
|
||||
@ -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<Camera>(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<float>(height));
|
||||
auto uishader = assets->get<Shader>("ui");
|
||||
uishader->use();
|
||||
uishader->uniformMatrix("u_projview", uicamera->getProjView());
|
||||
|
||||
auto bg = assets->get<Texture>("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();
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -126,7 +126,15 @@ void Batch3D::sprite(
|
||||
float scale = 1.0f / static_cast<float>(atlasRes);
|
||||
float u = (index % atlasRes) * scale;
|
||||
float v = 1.0f - ((index / atlasRes) * scale) - scale;
|
||||
sprite(pos, up, right, w, h, UVRegion(u, v, u+scale, v+scale), tint);
|
||||
sprite(
|
||||
pos + right * w + up * h, // revert centering
|
||||
up,
|
||||
right,
|
||||
w,
|
||||
h,
|
||||
UVRegion(u, v, u + scale, v + scale),
|
||||
tint
|
||||
);
|
||||
}
|
||||
|
||||
void Batch3D::sprite(
|
||||
|
||||
@ -171,6 +171,9 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
|
||||
if (mesh == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (chunk->flags.dirtyHeights) {
|
||||
chunk->updateHeights();
|
||||
}
|
||||
if (culling) {
|
||||
glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D);
|
||||
glm::vec3 max(
|
||||
|
||||
@ -51,6 +51,7 @@ void TextsRenderer::renderNote(
|
||||
glm::vec3 yvec = note.getAxisY();
|
||||
|
||||
int width = font.calcWidth(text, text.length());
|
||||
int height = font.getLineHeight();
|
||||
if (preset.displayMode == NoteDisplayMode::Y_FREE_BILLBOARD ||
|
||||
preset.displayMode == NoteDisplayMode::XY_FREE_BILLBOARD) {
|
||||
xvec = camera.position - pos;
|
||||
@ -96,8 +97,11 @@ void TextsRenderer::renderNote(
|
||||
|
||||
pos = screenPos / screenPos.w;
|
||||
}
|
||||
} else if (!frustum.isBoxVisible(pos - xvec * (width * 0.5f * preset.scale),
|
||||
pos + xvec * (width * 0.5f * preset.scale))) {
|
||||
} else if (!frustum.isBoxVisible(
|
||||
pos - xvec * (width * 0.5f) * preset.scale,
|
||||
pos + xvec * (width * 0.5f) * preset.scale +
|
||||
yvec * static_cast<float>(height) * preset.scale
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
auto color = preset.color;
|
||||
|
||||
@ -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<UiDocument>(
|
||||
"core:root",
|
||||
uidocscript {},
|
||||
std::dynamic_pointer_cast<gui::UINode>(container),
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
GUI::~GUI() = default;
|
||||
@ -74,15 +81,8 @@ std::shared_ptr<Menu> GUI::getMenu() {
|
||||
}
|
||||
|
||||
void GUI::onAssetsLoad(Assets* assets) {
|
||||
assets->store(
|
||||
std::make_unique<UiDocument>(
|
||||
"core:root",
|
||||
uidocscript {},
|
||||
std::dynamic_pointer_cast<gui::UINode>(container),
|
||||
nullptr
|
||||
),
|
||||
"core:root"
|
||||
);
|
||||
rootDocument->rebuildIndices();
|
||||
assets->store(rootDocument, "core:root");
|
||||
}
|
||||
|
||||
void GUI::resetTooltip() {
|
||||
@ -302,6 +302,7 @@ bool GUI::isFocusCaught() const {
|
||||
}
|
||||
|
||||
void GUI::add(std::shared_ptr<UINode> node) {
|
||||
UINode::getIndices(node, rootDocument->getMapWriteable());
|
||||
container->add(std::move(node));
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@ namespace devtools {
|
||||
class Editor;
|
||||
}
|
||||
|
||||
class UiDocument;
|
||||
|
||||
/*
|
||||
Some info about padding and margin.
|
||||
Padding is element inner space, margin is outer
|
||||
@ -70,6 +72,7 @@ namespace gui {
|
||||
std::shared_ptr<UINode> pressed;
|
||||
std::shared_ptr<UINode> focus;
|
||||
std::shared_ptr<UINode> tooltip;
|
||||
std::shared_ptr<UiDocument> rootDocument;
|
||||
std::unordered_map<std::string, std::shared_ptr<UINode>> storage;
|
||||
|
||||
std::unique_ptr<Camera> uicamera;
|
||||
|
||||
@ -810,6 +810,85 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
static int calc_indent(int linestart, std::wstring_view input) {
|
||||
int indent = 0;
|
||||
while (linestart + indent < input.length() &&
|
||||
input[linestart + indent] == L' ')
|
||||
indent++;
|
||||
return indent;
|
||||
}
|
||||
|
||||
void TextBox::onTab(bool shiftPressed) {
|
||||
std::wstring indentStr = L" ";
|
||||
|
||||
if (!shiftPressed && getSelectionLength() == 0) {
|
||||
paste(indentStr);
|
||||
return;
|
||||
}
|
||||
if (getSelectionLength() == 0) {
|
||||
selectionStart = caret;
|
||||
selectionEnd = caret;
|
||||
selectionOrigin = caret;
|
||||
}
|
||||
|
||||
int lineA = getLineAt(selectionStart);
|
||||
int lineB = getLineAt(selectionEnd);
|
||||
int caretLine = getLineAt(caret);
|
||||
|
||||
size_t lineAStart = getLinePos(lineA);
|
||||
size_t lineBStart = getLinePos(lineB);
|
||||
size_t caretLineStart = getLinePos(caretLine);
|
||||
size_t caretIndent = calc_indent(caretLineStart, input);
|
||||
size_t aIndent = calc_indent(lineAStart, input);
|
||||
size_t bIndent = calc_indent(lineBStart, input);
|
||||
|
||||
int lastSelectionStart = selectionStart;
|
||||
int lastSelectionEnd = selectionEnd;
|
||||
size_t lastCaret = caret;
|
||||
|
||||
auto combination = history->beginCombination();
|
||||
|
||||
resetSelection();
|
||||
|
||||
for (int line = lineA; line <= lineB; line++) {
|
||||
size_t linestart = getLinePos(line);
|
||||
int indent = calc_indent(linestart, input);
|
||||
|
||||
if (shiftPressed) {
|
||||
if (indent >= indentStr.length()) {
|
||||
setCaret(linestart);
|
||||
select(linestart, linestart + indentStr.length());
|
||||
eraseSelected();
|
||||
}
|
||||
} else {
|
||||
setCaret(linestart);
|
||||
paste(indentStr);
|
||||
}
|
||||
refreshLabel(); // todo: replace with textbox cache
|
||||
}
|
||||
|
||||
int linestart = getLinePos(caretLine);
|
||||
int linestartA = getLinePos(lineA);
|
||||
int linestartB = getLinePos(lineB);
|
||||
int la = lastSelectionStart - lineAStart;
|
||||
int lb = lastSelectionEnd - lineBStart;
|
||||
if (shiftPressed) {
|
||||
setCaret(lastCaret - caretLineStart + linestart - std::min<int>(caretIndent, indentStr.length()));
|
||||
selectionStart = la + linestartA - std::min<int>(std::min<int>(la, aIndent), indentStr.length());
|
||||
selectionEnd = lb + linestartB - std::min<int>(std::min<int>(lb, bIndent), indentStr.length());
|
||||
} else {
|
||||
setCaret(lastCaret - caretLineStart + linestart + indentStr.length());
|
||||
selectionStart = la + linestartA + indentStr.length();
|
||||
selectionEnd = lb + linestartB + indentStr.length();
|
||||
}
|
||||
if (selectionOrigin == lastSelectionStart) {
|
||||
selectionOrigin = selectionStart;
|
||||
} else {
|
||||
selectionOrigin = selectionEnd;
|
||||
}
|
||||
historian->sync();
|
||||
}
|
||||
|
||||
void TextBox::refreshSyntax() {
|
||||
if (!syntax.empty()) {
|
||||
const auto& processor = gui.getEditor().getSyntaxProcessor();
|
||||
@ -868,7 +947,7 @@ void TextBox::performEditingKeyboardEvents(Keycode key) {
|
||||
}
|
||||
}
|
||||
} else if (key == Keycode::TAB) {
|
||||
paste(L" ");
|
||||
onTab(shiftPressed);
|
||||
} else if (key == Keycode::LEFT) {
|
||||
stepLeft(shiftPressed, breakSelection);
|
||||
} else if (key == Keycode::RIGHT) {
|
||||
|
||||
@ -71,6 +71,8 @@ namespace gui {
|
||||
void stepDefaultDown(bool shiftPressed, bool breakSelection);
|
||||
void stepDefaultUp(bool shiftPressed, bool breakSelection);
|
||||
|
||||
void onTab(bool shiftPressed);
|
||||
|
||||
size_t normalizeIndex(int index);
|
||||
|
||||
int calcIndexAt(int x, int y) const;
|
||||
|
||||
@ -89,6 +89,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
||||
builder.section("debug");
|
||||
builder.add("generator-test-mode", &settings.debug.generatorTestMode);
|
||||
builder.add("do-write-lights", &settings.debug.doWriteLights);
|
||||
builder.add("enable-experimental", &settings.debug.enableExperimental);
|
||||
}
|
||||
|
||||
dv::value SettingsHandler::getValue(const std::string& name) const {
|
||||
|
||||
@ -54,7 +54,7 @@ static int l_get_gravity_scale(lua::State* L) {
|
||||
|
||||
static int l_set_gravity_scale(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
entity->getRigidbody().hitbox.gravityScale = lua::tonumber(L, 2);
|
||||
entity->getRigidbody().hitbox.gravityScale = lua::tovec3(L, 2).y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -23,6 +23,9 @@ static void load_texture(
|
||||
}
|
||||
|
||||
static int l_load_texture(lua::State* L) {
|
||||
if (lua::isstring(L, 3) && lua::require_lstring(L, 3) != "png") {
|
||||
throw std::runtime_error("unsupportd image format");
|
||||
}
|
||||
if (lua::istable(L, 1)) {
|
||||
lua::pushvalue(L, 1);
|
||||
size_t size = lua::objlen(L, 1);
|
||||
|
||||
@ -101,12 +101,9 @@ static int l_set(lua::State* L) {
|
||||
if (static_cast<size_t>(id) >= indices->blocks.count()) {
|
||||
return 0;
|
||||
}
|
||||
int cx = floordiv<CHUNK_W>(x);
|
||||
int cz = floordiv<CHUNK_D>(z);
|
||||
if (!blocks_agent::get_chunk(*level->chunks, cx, cz)) {
|
||||
if (!blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state))) {
|
||||
return 0;
|
||||
}
|
||||
blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state));
|
||||
|
||||
auto chunksController = controller->getChunksController();
|
||||
if (chunksController == nullptr) {
|
||||
|
||||
@ -79,9 +79,12 @@ static int l_textbox_paste(lua::State* L) {
|
||||
|
||||
static int l_container_add(lua::State* L) {
|
||||
auto docnode = get_document_node(L);
|
||||
if (docnode.document == nullptr) {
|
||||
throw std::runtime_error("target document not found");
|
||||
}
|
||||
auto node = dynamic_cast<Container*>(docnode.node.get());
|
||||
if (node == nullptr) {
|
||||
return 0;
|
||||
throw std::runtime_error("target container not found");
|
||||
}
|
||||
auto xmlsrc = lua::require_string(L, 2);
|
||||
try {
|
||||
@ -99,7 +102,7 @@ static int l_container_add(lua::State* L) {
|
||||
UINode::getIndices(subnode, docnode.document->getMapWriteable());
|
||||
node->add(std::move(subnode));
|
||||
} catch (const std::exception& err) {
|
||||
throw std::runtime_error(err.what());
|
||||
throw std::runtime_error("container:add(...): " + std::string(err.what()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ static int l_open(lua::State* L) {
|
||||
}
|
||||
return lua::pushinteger(L, hud->openInventory(
|
||||
layout,
|
||||
level->inventories->get(invid),
|
||||
lua::isnoneornil(L, 3) ? nullptr : level->inventories->get(invid),
|
||||
playerInventory
|
||||
)->getId());
|
||||
}
|
||||
|
||||
@ -45,6 +45,12 @@ namespace {
|
||||
|
||||
template <SlotFunc func>
|
||||
int wrap_slot(lua::State* L) {
|
||||
if (lua::isnoneornil(L, 1)) {
|
||||
throw std::runtime_error("inventory id is nil");
|
||||
}
|
||||
if (lua::isnoneornil(L, 2)) {
|
||||
throw std::runtime_error("slot index is nil");
|
||||
}
|
||||
auto invid = lua::tointeger(L, 1);
|
||||
auto slotid = lua::tointeger(L, 2);
|
||||
auto& inv = get_inventory(invid);
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "interfaces/Process.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
@ -112,11 +113,47 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Process> scripting::start_coroutine(
|
||||
class LuaProjectScript : public IClientProjectScript {
|
||||
public:
|
||||
LuaProjectScript(lua::State* L, scriptenv env) : L(L), env(std::move(env)) {}
|
||||
|
||||
void onScreenChange(const std::string& name, bool show) override {
|
||||
if (!lua::pushenv(L, *env)) {
|
||||
return;
|
||||
}
|
||||
if (!lua::getfield(L, "on_" + name + (show ? "_setup" : "_clear"))) {
|
||||
lua::pop(L);
|
||||
return;
|
||||
}
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
private:
|
||||
lua::State* L;
|
||||
scriptenv env;
|
||||
};
|
||||
|
||||
std::unique_ptr<IClientProjectScript> scripting::load_client_project_script(
|
||||
const io::path& script
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
if (lua::getglobal(L, "__vc_start_coroutine")) {
|
||||
auto source = io::read_string(script);
|
||||
auto env = create_environment(nullptr);
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::getglobal(L, "__vc_app")) {
|
||||
lua::setfield(L, "app");
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
lua::loadbuffer(L, *env, source, script.name());
|
||||
lua::call(L, 0);
|
||||
return std::make_unique<LuaProjectScript>(L, std::move(env));
|
||||
}
|
||||
|
||||
std::unique_ptr<Process> scripting::start_coroutine(const io::path& script) {
|
||||
auto L = lua::get_main_state();
|
||||
auto method = "__vc_start_coroutine";
|
||||
if (lua::getglobal(L, method)) {
|
||||
auto source = io::read_string(script);
|
||||
lua::loadbuffer(L, 0, source, script.name());
|
||||
if (lua::call(L, 1)) {
|
||||
@ -259,7 +296,11 @@ void scripting::on_world_load(LevelController* controller) {
|
||||
}
|
||||
|
||||
for (auto& pack : content_control->getAllContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ":.worldopen");
|
||||
lua::emit_event(L, pack.id + ":.worldopen", [](auto L) {
|
||||
return lua::pushboolean(
|
||||
L, !scripting::level->getWorld()->getInfo().isLoaded
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -65,10 +65,19 @@ namespace scripting {
|
||||
|
||||
void process_post_runnables();
|
||||
|
||||
std::unique_ptr<Process> start_coroutine(
|
||||
class IClientProjectScript {
|
||||
public:
|
||||
virtual ~IClientProjectScript() {}
|
||||
|
||||
virtual void onScreenChange(const std::string& name, bool show) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IClientProjectScript> load_client_project_script(
|
||||
const io::path& script
|
||||
);
|
||||
|
||||
std::unique_ptr<Process> start_coroutine(const io::path& script);
|
||||
|
||||
void on_world_load(LevelController* controller);
|
||||
void on_world_tick(int tps);
|
||||
void on_world_save();
|
||||
|
||||
@ -95,6 +95,8 @@ struct DebugSettings {
|
||||
FlagSetting generatorTestMode {false};
|
||||
/// @brief Write lights cache
|
||||
FlagSetting doWriteLights {true};
|
||||
/// @brief Enable experimental optimizations and features
|
||||
FlagSetting enableExperimental {false};
|
||||
};
|
||||
|
||||
struct UiSettings {
|
||||
|
||||
@ -14,6 +14,7 @@ Chunk::Chunk(int xpos, int zpos) : x(xpos), z(zpos) {
|
||||
}
|
||||
|
||||
void Chunk::updateHeights() {
|
||||
flags.dirtyHeights = false;
|
||||
for (uint i = 0; i < CHUNK_VOL; i++) {
|
||||
if (voxels[i].id != 0) {
|
||||
bottom = i / (CHUNK_D * CHUNK_W);
|
||||
|
||||
@ -37,6 +37,7 @@ public:
|
||||
bool loadedLights : 1;
|
||||
bool entities : 1;
|
||||
bool blocksData : 1;
|
||||
bool dirtyHeights : 1;
|
||||
} flags {};
|
||||
|
||||
/// @brief Block inventories map where key is index of block in voxels array
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
using namespace blocks_agent;
|
||||
|
||||
template <class Storage>
|
||||
static inline void set_block(
|
||||
static inline bool set_block(
|
||||
Storage& chunks,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
@ -16,14 +16,14 @@ static inline void set_block(
|
||||
blockstate state
|
||||
) {
|
||||
if (y < 0 || y >= CHUNK_H) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const auto& indices = chunks.getContentIndices();
|
||||
int cx = floordiv<CHUNK_W>(x);
|
||||
int cz = floordiv<CHUNK_D>(z);
|
||||
Chunk* chunk = get_chunk(chunks, cx, cz);
|
||||
if (chunk == nullptr) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
int lx = x - cx * CHUNK_W;
|
||||
int lz = z - cz * CHUNK_D;
|
||||
@ -60,7 +60,8 @@ static inline void set_block(
|
||||
else if (y + 1 > chunk->top)
|
||||
chunk->top = y + 1;
|
||||
else if (id == 0)
|
||||
chunk->updateHeights();
|
||||
chunk->flags.dirtyHeights = true;
|
||||
|
||||
|
||||
if (lx == 0 && (chunk = get_chunk(chunks, cx - 1, cz))) {
|
||||
chunk->flags.modified = true;
|
||||
@ -74,9 +75,10 @@ static inline void set_block(
|
||||
if (lz == CHUNK_D - 1 && (chunk = get_chunk(chunks, cx, cz + 1))) {
|
||||
chunk->flags.modified = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void blocks_agent::set(
|
||||
bool blocks_agent::set(
|
||||
Chunks& chunks,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
@ -84,10 +86,10 @@ void blocks_agent::set(
|
||||
uint32_t id,
|
||||
blockstate state
|
||||
) {
|
||||
set_block(chunks, x, y, z, id, state);
|
||||
return set_block(chunks, x, y, z, id, state);
|
||||
}
|
||||
|
||||
void blocks_agent::set(
|
||||
bool blocks_agent::set(
|
||||
GlobalChunks& chunks,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
@ -95,7 +97,7 @@ void blocks_agent::set(
|
||||
uint32_t id,
|
||||
blockstate state
|
||||
) {
|
||||
set_block(chunks, x, y, z, id, state);
|
||||
return set_block(chunks, x, y, z, id, state);
|
||||
}
|
||||
|
||||
template <class Storage>
|
||||
|
||||
@ -119,7 +119,7 @@ inline bool is_replaceable_at(const Storage& chunks, int32_t x, int32_t y, int32
|
||||
/// @param z block position Z
|
||||
/// @param id new block id
|
||||
/// @param state new block state
|
||||
void set(
|
||||
bool set(
|
||||
Chunks& chunks,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
@ -135,7 +135,7 @@ void set(
|
||||
/// @param z block position Z
|
||||
/// @param id new block id
|
||||
/// @param state new block state
|
||||
void set(
|
||||
bool set(
|
||||
GlobalChunks& chunks,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
|
||||
@ -116,6 +116,8 @@ std::unique_ptr<Level> World::load(
|
||||
if (!info.has_value()) {
|
||||
throw world_load_error("could not to find world.json");
|
||||
}
|
||||
info->isLoaded = true;
|
||||
|
||||
logger.info() << "loading world " << info->name << " ("
|
||||
<< worldFilesPtr->getFolder().string() << ")";
|
||||
logger.info() << "world version: " << info->major << "." << info->minor
|
||||
|
||||
@ -45,6 +45,8 @@ struct WorldInfo : public Serializable {
|
||||
|
||||
int major = 0, minor = -1;
|
||||
|
||||
bool isLoaded = false;
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user