diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md
index 4cac91b7..c6da89c4 100644
--- a/doc/en/scripting/ui.md
+++ b/doc/en/scripting/ui.md
@@ -79,6 +79,7 @@ Properties:
| hint | string | yes | yes | text to display when nothing is entered |
| caret | int | yes | yes | carriage position. `textbox.caret = -1` will set the position to the end of the text |
| editable | bool | yes | yes | text mutability |
+| edited | bool | yes | yes\* | is text edited since the last set / edited status reset |
| multiline | bool | yes | yes | multiline support |
| lineNumbers | bool | yes | yes | display line numbers |
| textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") |
@@ -87,6 +88,8 @@ Properties:
| syntax | string | yes | yes | syntax highlighting ("lua" - Lua) |
| markup | string | yes | yes | text markup language ("md" - Markdown) |
+\* - false only
+
Methods:
| Method | Description |
diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md
index 6f687328..64579db1 100644
--- a/doc/en/xml-ui-layouts.md
+++ b/doc/en/xml-ui-layouts.md
@@ -110,6 +110,8 @@ Inner text - initially entered text
- `supplier` - text supplier (called every frame)
- `consumer` - lua function that receives the entered text. Called only when input is complete
- `sub-consumer` - lua function-receiver of the input text. Called during text input or deletion.
+- `oncontrolkey` - lua function called for combinations of the form (Ctrl + ?). The codepoint of the second key is given as the first argument.
+The key code for comparison can be obtained via `input.keycode("key_name")`
- `autoresize` - automatic change of element size (default - false). Does not affect font size.
- `multiline` - allows display of multiline text.
- `text-wrap` - allows automatic text wrapping (works only with multiline: "true")
diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md
index 137ff57b..62697c8f 100644
--- a/doc/ru/scripting/ui.md
+++ b/doc/ru/scripting/ui.md
@@ -79,6 +79,7 @@ document["worlds-panel"]:clear()
| hint | string | да | да | текст, отображаемый, когда ничего не введено |
| caret | int | да | да | позиция каретки. `textbox.caret = -1` установит позицию в конец текста |
| editable | bool | да | да | изменяемость текста |
+| edited | bool | да | да\* | был ли изменён текст с последней установки/сброса свойства |
| multiline | bool | да | да | поддержка многострочности |
| lineNumbers | bool | да | да | отображение номеров строк |
| textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") |
@@ -87,6 +88,8 @@ document["worlds-panel"]:clear()
| syntax | string | да | да | подсветка синтаксиса ("lua" - Lua) |
| markup | string | да | да | язык разметки текста ("md" - Markdown) |
+\* - только false
+
Методы:
| Метод | Описание |
diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md
index 30c17315..e0524793 100644
--- a/doc/ru/xml-ui-layouts.md
+++ b/doc/ru/xml-ui-layouts.md
@@ -111,6 +111,8 @@
- `supplier` - поставщик текста (вызывается каждый кадр)
- `consumer` - lua функция-приемник введенного текста. Вызывается только при завершении ввода
- `sub-consumer` - lua функция-приемник вводимого текста. Вызывается во время ввода или удаления текста.
+- `oncontrolkey` - lua функция вызываемая для сочетаний вида (Ctrl + ?). На вход подаётся числовой код второй клавиши.
+Код клавиши для сравнения можно получить через `input.keycode("имя_клавиши")`
- `autoresize` - автоматическое изменение размера элемента (по-умолчанию - false). Не влияет на размер шрифта.
- `multiline` - разрешает отображение многострочного текста.
- `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true")
diff --git a/res/layouts/console.xml b/res/layouts/console.xml
index 18d72163..bc5fb343 100644
--- a/res/layouts/console.xml
+++ b/res/layouts/console.xml
@@ -2,17 +2,14 @@
+ size-func="gui.get_viewport()[1],30">
-
-
-
+ size-func="unpack(vec2.add(gui.get_viewport(), {-450,-100}))">
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
%s: %s",
+ gui.str("Error at line %{0}"):gsub("%%{0}", line), message)
+ )
+ return
+ end
+ local info = registry.get_info(current_file.filename)
+ local script_type = info and info.type or "file"
+ local unit = info and info.unit
+ save_current_file()
+
+ local func = function()
+ local stack_size = debug.count_frames()
+ xpcall(chunk, function(msg) __vc__error(msg, 1, 1, stack_size) end)
+ end
+
+ local funcs = {
+ block = block.reload_script,
+ item = item.reload_script,
+ world = world.reload_script,
+ hud = hud.reload_script,
+ component = entities.reload_component,
+ module = reload_module,
+ }
+ func = funcs[script_type] or func
+ local output = core.capture_output(function() func(unit) end)
+ document.output:add(
+ string.format(
+ "",
+ output)
+ )
+end
+
+function save_current_file()
+ if not current_file.mutable then
+ return
+ end
+ file.write(current_file.mutable, document.editor.text)
+ current_file.modified = false
+ document.saveIcon.enabled = false
+ document.title.text = gui.str('File')..' - '..current_file.filename
+ document.editor.edited = false
+end
+
+function open_file_in_editor(filename, line, mutable)
+ local editor = document.editor
+ local source = file.read(filename):gsub('\t', ' ')
+ editor.text = source
+ editor.focused = true
+ if line then
+ time.post_runnable(function()
+ editor.caret = editor:linePos(line)
+ end)
+ end
+ document.title.text = gui.str('File') .. ' - ' .. filename
+ current_file.filename = filename
+ current_file.mutable = mutable or find_mutable(filename)
+ document.lockIcon.visible = current_file.mutable == nil
+ document.editor.editable = current_file.mutable ~= nil
+ document.saveIcon.enabled = current_file.modified
+end
+
+function clear_traceback()
+ local tb_list = document.traceback
+ tb_list:clear()
+ tb_list:add("")
+end
+
+function clear_output()
+ local output = document.output
+ output:clear()
+ output:add("")
+end
+
events.on("core:open_traceback", function(traceback_b64)
local traceback = bjson.frombytes(base64.decode(traceback_b64))
modes:set('debug')
+ clear_traceback()
+
local tb_list = document.traceback
local srcsize = tb_list.size
- tb_list:clear()
- tb_list:add("")
for _, frame in ipairs(traceback.frames) do
local callback = ""
local framestr = ""
@@ -62,23 +241,12 @@ events.on("core:open_traceback", function(traceback_b64)
framestr = frame.source..":"..tostring(frame.currentline).." "
if file.exists(frame.source) then
callback = string.format(
- "local editor = document.editor "..
- "local source = file.read('%s'):gsub('\t', ' ') "..
- "editor.text = source "..
- "editor.focused = true "..
- "time.post_runnable(function()"..
- "editor.caret = editor:linePos(%s) "..
- "end)",
+ "open_file_in_editor('%s', %s)",
frame.source, frame.currentline-1
)
else
callback = "document.editor.text = 'Could not open source file'"
end
- callback = string.format(
- "%s document.title.text = gui.str('File')..' - %s'",
- callback,
- frame.source
- )
end
if frame.name then
framestr = framestr.."("..tostring(frame.name)..")"
@@ -184,7 +352,8 @@ end
function set_mode(mode)
local show_prompt = mode == 'chat' or mode == 'console'
- document.title.text = ""
+ document.lockIcon.visible = false
+ document.editorRoot.visible = mode == 'debug'
document.editorContainer.visible = mode == 'debug'
document.logContainer.visible = mode ~= 'debug'
@@ -194,7 +363,6 @@ function set_mode(mode)
document.root.color = {0, 0, 0, 128}
end
- document.traceback.visible = mode == 'debug'
document.prompt.visible = show_prompt
if show_prompt then
document.prompt.focused = true
@@ -211,6 +379,17 @@ function on_open(mode)
}, function (mode)
set_mode(mode)
end, mode or "console")
+
+ local files_list = document.filesList
+
+ filenames = registry.filenames
+ table.sort(filenames)
+ build_files_list(filenames)
+
+ document.editorContainer:setInterval(200, refresh_file_title)
+
+ clear_traceback()
+ clear_output()
elseif mode then
modes:set(mode)
end
diff --git a/res/layouts/templates/problem.xml b/res/layouts/templates/problem.xml
index 17085392..f4382dbb 100644
--- a/res/layouts/templates/problem.xml
+++ b/res/layouts/templates/problem.xml
@@ -1,9 +1,7 @@
-
-
-
+
diff --git a/res/layouts/templates/script_file.xml b/res/layouts/templates/script_file.xml
new file mode 100644
index 00000000..94e1afd2
--- /dev/null
+++ b/res/layouts/templates/script_file.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/res/modules/internal/scripts_registry.lua b/res/modules/internal/scripts_registry.lua
new file mode 100644
index 00000000..de685224
--- /dev/null
+++ b/res/modules/internal/scripts_registry.lua
@@ -0,0 +1,74 @@
+local export = {}
+
+local function collect_components(dirname, dest)
+ if file.isdir(dirname) then
+ local files = file.list(dirname)
+ for i, filename in ipairs(files) do
+ if file.ext(filename) == "lua" then
+ table.insert(dest, filename)
+ export.classification[filename] = {
+ type="component",
+ unit=file.prefix(filename)..":"..file.stem(filename)
+ }
+ end
+ end
+ end
+end
+
+local function collect_scripts(dirname, dest, ismodule)
+ if file.isdir(dirname) then
+ local files = file.list(dirname)
+ for i, filename in ipairs(files) do
+ if file.name(filename) == "components" and not ismodule then
+ collect_components(filename, dest)
+ elseif file.isdir(filename) then
+ collect_scripts(filename, dest)
+ elseif file.ext(filename) == "lua" then
+ table.insert(dest, filename)
+ end
+ end
+ end
+end
+
+local function load_scripts_list()
+ local packs = pack.get_installed()
+ for _, packid in ipairs(packs) do
+ collect_scripts(packid..":modules", export.filenames, true)
+ end
+ for _, filename in ipairs(export.filenames) do
+ export.classification[filename] = {
+ type="module",
+ unit=file.join(file.parent(file.prefix(filename)..":"..
+ filename:sub(filename:find("/")+1)),
+ file.stem(filename))
+ }
+ end
+ for _, packid in ipairs(packs) do
+ collect_scripts(packid..":scripts", export.filenames, false)
+ end
+end
+
+function export.build_classification()
+ local classification = {}
+ for id, props in pairs(block.properties) do
+ classification[props["script-file"]] = {type="block", unit=block.name(id)}
+ end
+ for id, props in pairs(item.properties) do
+ classification[props["script-file"]] = {type="item", unit=item.name(id)}
+ end
+ local packs = pack.get_installed()
+ for _, packid in ipairs(packs) do
+ classification[packid..":scripts/world.lua"] = {type="world", unit=packid}
+ classification[packid..":scripts/hud.lua"] = {type="hud", unit=packid}
+ end
+ export.classification = classification
+ export.filenames = {}
+
+ load_scripts_list()
+end
+
+function export.get_info(filename)
+ return export.classification[filename]
+end
+
+return export
diff --git a/res/preload.json b/res/preload.json
index c4e60cad..1cfd0747 100644
--- a/res/preload.json
+++ b/res/preload.json
@@ -24,7 +24,18 @@
"misc/snow",
"gui/check_mark",
"gui/left_arrow",
- "gui/right_arrow"
+ "gui/right_arrow",
+ "gui/lock",
+ "gui/save",
+ "gui/block",
+ "gui/item",
+ "gui/file",
+ "gui/module",
+ "gui/play",
+ "gui/info",
+ "gui/world",
+ "gui/hud",
+ "gui/entity"
],
"fonts": [
{
diff --git a/res/scripts/post_content.lua b/res/scripts/post_content.lua
index 8725ed97..85d7b28a 100644
--- a/res/scripts/post_content.lua
+++ b/res/scripts/post_content.lua
@@ -7,7 +7,7 @@ local names = {
"hidden", "draw-group", "picking-item", "surface-replacement", "script-name",
"ui-layout", "inventory-size", "tick-interval", "overlay-texture",
"translucent", "fields", "particles", "icon-type", "icon", "placing-block",
- "stack-size", "name"
+ "stack-size", "name", "script-file"
}
for name, _ in pairs(user_props) do
table.insert(names, name)
@@ -61,3 +61,6 @@ end
cache_names(block)
cache_names(item)
+
+local scripts_registry = require "core:internal/scripts_registry"
+scripts_registry.build_classification()
diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua
index e2e4d3c7..1366966a 100644
--- a/res/scripts/stdmin.lua
+++ b/res/scripts/stdmin.lua
@@ -429,6 +429,18 @@ function file.readlines(path)
return lines
end
+function debug.count_frames()
+ local frames = 1
+ while true do
+ local info = debug.getinfo(frames)
+ if info then
+ frames = frames + 1
+ else
+ return frames - 1
+ end
+ end
+end
+
function debug.get_traceback(start)
local frames = {}
local n = 2 + (start or 0)
@@ -463,6 +475,35 @@ function on_deprecated_call(name, alternatives)
end
end
+function reload_module(name)
+ local prefix, name = parse_path(name)
+ local path = prefix..":modules/"..name..".lua"
+
+ local previous = package.loaded[path]
+ if not previous then
+ debug.log("attempt to reload non-loaded module "..name.." ("..path..")")
+ return
+ end
+ local script, err = load(file.read(path), path)
+ if script == nil then
+ error(err)
+ end
+ local result = script()
+ if not result then
+ return
+ end
+ for i, value in ipairs(result) do
+ previous[i] = value
+ end
+ local copy = table.copy(result)
+ for key, value in pairs(result) do
+ result[key] = nil
+ end
+ for key, value in pairs(copy) do
+ previous[key] = value
+ end
+end
+
-- Load script with caching
--
-- path - script path `contentpack:filename`.
@@ -513,9 +554,13 @@ function __scripts_cleanup()
end
end
-function __vc__error(msg, frame)
+function __vc__error(msg, frame, n, lastn)
if events then
- events.emit("core:error", msg, debug.get_traceback(1))
+ local frames = debug.get_traceback(1)
+ events.emit(
+ "core:error", msg,
+ table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
+ )
end
return debug.traceback(msg, frame)
end
@@ -543,3 +588,23 @@ end
function file.prefix(path)
return path:match("^([^:]+)")
end
+
+function file.parent(path)
+ local dir = path:match("(.*)/")
+ if not dir then
+ return file.prefix(path)..":"
+ end
+ return dir
+end
+
+function file.path(path)
+ local pos = path:find(':')
+ return path:sub(pos + 1)
+end
+
+function file.join(a, b)
+ if a[#a] == ':' then
+ return a .. b
+ end
+ return a .. "/" .. b
+end
diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt
index 732e2d66..57a6cb6b 100644
--- a/res/texts/en_US.txt
+++ b/res/texts/en_US.txt
@@ -11,7 +11,9 @@ world.delete-confirm=Do you want to delete world forever?
world.generators.default=Default
world.generators.flat=Flat
+editor.info.tooltip=CTRL+S - Save\nCTRL+R - Run\nCTRL+Z - Undo\nCTRL+Y - Redo
devtools.traceback=Traceback (most recent call first)
+devtools.output=Output
# Tooltips
graphics.gamma.tooltip=Lighting brightness curve
diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt
index 80a7ba6a..65db4507 100644
--- a/res/texts/ru_RU.txt
+++ b/res/texts/ru_RU.txt
@@ -19,8 +19,16 @@ Problems=Проблемы
Monitor=Мониторинг
Debug=Отладка
File=Файл
+Read only=Только для чтения
+Save=Сохранить
+Grant %{0} pack modification permission?=Выдать разрешение на модификацию пака %{0}?
+Error at line %{0}=Ошибка на строке %{0}
+Run=Запустить
+editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
devtools.traceback=Стек вызовов (от последнего)
+devtools.output=Вывод
+
error.pack-not-found=Не удалось найти пакет
error.dependency-not-found=Используемая зависимость не найдена
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?
diff --git a/res/textures/gui/block.png b/res/textures/gui/block.png
new file mode 100644
index 00000000..dfcc239a
Binary files /dev/null and b/res/textures/gui/block.png differ
diff --git a/res/textures/gui/entity.png b/res/textures/gui/entity.png
new file mode 100644
index 00000000..53207e8b
Binary files /dev/null and b/res/textures/gui/entity.png differ
diff --git a/res/textures/gui/file.png b/res/textures/gui/file.png
new file mode 100644
index 00000000..3d5b5c21
Binary files /dev/null and b/res/textures/gui/file.png differ
diff --git a/res/textures/gui/hud.png b/res/textures/gui/hud.png
new file mode 100644
index 00000000..4d32d616
Binary files /dev/null and b/res/textures/gui/hud.png differ
diff --git a/res/textures/gui/info.png b/res/textures/gui/info.png
new file mode 100644
index 00000000..a7bb7030
Binary files /dev/null and b/res/textures/gui/info.png differ
diff --git a/res/textures/gui/item.png b/res/textures/gui/item.png
new file mode 100644
index 00000000..8714b10f
Binary files /dev/null and b/res/textures/gui/item.png differ
diff --git a/res/textures/gui/lock.png b/res/textures/gui/lock.png
new file mode 100644
index 00000000..10c877a3
Binary files /dev/null and b/res/textures/gui/lock.png differ
diff --git a/res/textures/gui/module.png b/res/textures/gui/module.png
new file mode 100644
index 00000000..84922d72
Binary files /dev/null and b/res/textures/gui/module.png differ
diff --git a/res/textures/gui/play.png b/res/textures/gui/play.png
new file mode 100644
index 00000000..2442b02c
Binary files /dev/null and b/res/textures/gui/play.png differ
diff --git a/res/textures/gui/save.png b/res/textures/gui/save.png
new file mode 100644
index 00000000..8fc66b5a
Binary files /dev/null and b/res/textures/gui/save.png differ
diff --git a/res/textures/gui/world.png b/res/textures/gui/world.png
new file mode 100644
index 00000000..2f0320c3
Binary files /dev/null and b/res/textures/gui/world.png differ
diff --git a/src/constants.hpp b/src/constants.hpp
index 9337513f..1accd619 100644
--- a/src/constants.hpp
+++ b/src/constants.hpp
@@ -68,3 +68,5 @@ inline const std::string LAYOUTS_FOLDER = "layouts";
inline const std::string SOUNDS_FOLDER = "sounds";
inline const std::string MODELS_FOLDER = "models";
inline const std::string SKELETONS_FOLDER = "skeletons";
+
+inline const std::string FONT_DEFAULT = "normal";
diff --git a/src/content/Content.cpp b/src/content/Content.cpp
index 1cc2fdd7..ce92be18 100644
--- a/src/content/Content.cpp
+++ b/src/content/Content.cpp
@@ -79,6 +79,14 @@ const ContentPackRuntime* Content::getPackRuntime(const std::string& id) const {
return found->second.get();
}
+ContentPackRuntime* Content::getPackRuntime(const std::string& id) {
+ auto found = packs.find(id);
+ if (found == packs.end()) {
+ return nullptr;
+ }
+ return found->second.get();
+}
+
const UptrsMap& Content::getBlockMaterials() const {
return blockMaterials;
}
diff --git a/src/content/Content.hpp b/src/content/Content.hpp
index b95ef247..94650b25 100644
--- a/src/content/Content.hpp
+++ b/src/content/Content.hpp
@@ -121,6 +121,14 @@ public:
return *found->second;
}
+ T& require(const std::string& id) {
+ const auto& found = defs.find(id);
+ if (found == defs.end()) {
+ throw std::runtime_error("missing content unit " + id);
+ }
+ return *found->second;
+ }
+
const auto& getDefs() const {
return defs;
}
@@ -240,6 +248,7 @@ public:
const rigging::SkeletonConfig* getSkeleton(const std::string& id) const;
const BlockMaterial* findBlockMaterial(const std::string& id) const;
const ContentPackRuntime* getPackRuntime(const std::string& id) const;
+ ContentPackRuntime* getPackRuntime(const std::string& id);
const UptrsMap& getBlockMaterials() const;
const UptrsMap& getPacks() const;
diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp
index c57a7ad8..caab68976 100644
--- a/src/content/ContentBuilder.cpp
+++ b/src/content/ContentBuilder.cpp
@@ -94,8 +94,9 @@ std::unique_ptr ContentBuilder::build() {
def->rt.surfaceReplacement = content->blocks.require(def->surfaceReplacement).rt.id;
if (def->properties == nullptr) {
def->properties = dv::object();
- def->properties["name"] = def->name;
}
+ def->properties["name"] = def->name;
+ def->properties["script-file"] = def->scriptFile;
}
for (ItemDef* def : itemDefsIndices) {
@@ -104,6 +105,7 @@ std::unique_ptr ContentBuilder::build() {
def->properties = dv::object();
}
def->properties["name"] = def->name;
+ def->properties["script-file"] = def->scriptFile;
}
for (auto& [name, def] : content->generators.getDefs()) {
diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp
index 9b14bc86..3fb2b8f6 100644
--- a/src/content/ContentLoader.cpp
+++ b/src/content/ContentLoader.cpp
@@ -394,6 +394,7 @@ void ContentLoader::loadBlock(
if (def.hidden && def.pickingItem == def.name + BLOCK_ITEM_SUFFIX) {
def.pickingItem = CORE_EMPTY;
}
+ def.scriptFile = pack->id + ":scripts/" + def.scriptName + ".lua";
}
void ContentLoader::loadItem(
@@ -452,6 +453,8 @@ void ContentLoader::loadItem(
def.emission[1] = emissionarr[1].asNumber();
def.emission[2] = emissionarr[2].asNumber();
}
+
+ def.scriptFile = pack->id + ":scripts/" + def.scriptName + ".lua";
}
void ContentLoader::loadEntity(
@@ -850,25 +853,54 @@ void ContentLoader::load() {
}
template
-static void load_scripts(Content& content, ContentUnitDefs& units) {
- for (const auto& [name, def] : units.getDefs()) {
- size_t pos = name.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& 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,
- scriptfile,
- pack.id + ":scripts/" + def->scriptName + ".lua",
- def->rt.funcsset
- );
- }
+static void load_script(const Content& content, T& def) {
+ const auto& name = def.name;
+ size_t pos = name.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& 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,
+ scriptfile,
+ def.scriptFile,
+ def.rt.funcsset
+ );
+ }
+}
+
+template
+static void load_scripts(const Content& content, ContentUnitDefs& units) {
+ for (const auto& [_, def] : units.getDefs()) {
+ load_script(content, *def);
+ }
+}
+
+void ContentLoader::reloadScript(const Content& content, Block& block) {
+ load_script(content, block);
+}
+
+void ContentLoader::reloadScript(const Content& content, ItemDef& item) {
+ load_script(content, item);
+}
+
+void ContentLoader::loadWorldScript(ContentPackRuntime& runtime) {
+ const auto& pack = runtime.getInfo();
+ const auto& folder = pack.folder;
+ io::path scriptFile = folder / "scripts/world.lua";
+ if (io::is_regular_file(scriptFile)) {
+ scripting::load_world_script(
+ runtime.getEnvironment(),
+ pack.id,
+ scriptFile,
+ pack.id + ":scripts/world.lua",
+ runtime.worldfuncsset
+ );
}
}
@@ -881,16 +913,8 @@ void ContentLoader::loadScripts(Content& content) {
const auto& folder = pack.folder;
// Load main world script
- io::path scriptFile = folder / "scripts/world.lua";
- if (io::is_regular_file(scriptFile)) {
- scripting::load_world_script(
- runtime->getEnvironment(),
- pack.id,
- scriptFile,
- pack.id + ":scripts/world.lua",
- runtime->worldfuncsset
- );
- }
+ loadWorldScript(*runtime);
+
// Load entity components
io::path componentsDir = folder / "scripts/components";
foreach_file(componentsDir, [&pack](const io::path& file) {
diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp
index f1135ee3..eeedf11b 100644
--- a/src/content/ContentLoader.hpp
+++ b/src/content/ContentLoader.hpp
@@ -77,4 +77,7 @@ public:
void load();
static void loadScripts(Content& content);
+ static void loadWorldScript(ContentPackRuntime& pack);
+ static void reloadScript(const Content& content, Block& block);
+ static void reloadScript(const Content& content, ItemDef& item);
};
diff --git a/src/delegates.hpp b/src/delegates.hpp
index 0e670b41..f583f951 100644
--- a/src/delegates.hpp
+++ b/src/delegates.hpp
@@ -15,6 +15,7 @@ using wstringsupplier = std::function;
using doublesupplier = std::function;
using boolsupplier = std::function;
using vec2supplier = std::function;
+using key_handler = std::function;
using stringconsumer = std::function;
using wstringconsumer = std::function;
diff --git a/src/devtools/actions.hpp b/src/devtools/actions.hpp
new file mode 100644
index 00000000..fffe9d67
--- /dev/null
+++ b/src/devtools/actions.hpp
@@ -0,0 +1,164 @@
+#pragma once
+
+#include
+#include
+
+class Action {
+public:
+ virtual ~Action() = default;
+
+ virtual void apply() = 0;
+ virtual void revert() = 0;
+};
+
+class InversedAction : public Action {
+public:
+ InversedAction(std::unique_ptr action) : action(std::move(action)) {}
+
+ void apply() override {
+ action->revert();
+ }
+
+ void revert() override {
+ action->apply();
+ }
+ private:
+ std::unique_ptr action;
+};
+
+class CombinedAction : public Action {
+public:
+ CombinedAction(std::vector> actions)
+ : actions(std::move(actions)) {
+ }
+
+ void apply() override {
+ for (auto& action : actions) {
+ action->apply();
+ }
+ }
+
+ void revert() override {
+ for (int i = actions.size() - 1; i >= 0; i--) {
+ actions[i]->revert();
+ }
+ }
+private:
+ std::vector> actions;
+};
+
+class ActionsHistory {
+public:
+ ActionsHistory() {};
+
+ /// @brief Remove all actions available to redo
+ void clearRedo() {
+ if (actionPtr < actions.size()) {
+ actions.erase(actions.begin() + actionPtr, actions.end());
+ }
+ }
+
+ /// @brief Store action without applying
+ void store(std::unique_ptr action, bool reverse=false) {
+ if (lock) {
+ return;
+ }
+ if (reverse) {
+ action = std::make_unique(std::move(action));
+ }
+ clearRedo();
+ actions.emplace_back(std::move(action));
+ actionPtr++;
+ }
+
+ /// @brief Apply action and store it
+ void apply(std::unique_ptr action) {
+ if (lock) {
+ return;
+ }
+ clearRedo();
+ lock = true;
+ action->apply();
+ lock = false;
+ actions.emplace_back(std::move(action));
+ actionPtr++;
+ }
+
+ /// @brief Revert the last action
+ /// @return true if any action reverted
+ bool undo() {
+ if (lock || actionPtr == 0) {
+ return false;
+ }
+ auto& action = actions[--actionPtr];
+ lock = true;
+ action->revert();
+ lock = false;
+ return true;
+ }
+
+ /// @brief Revert the last action
+ /// @return true if any action reapplied
+ bool redo() {
+ if (lock || actionPtr == actions.size()) {
+ return false;
+ }
+ auto& action = actions[actionPtr++];
+ lock = true;
+ action->apply();
+ lock = false;
+ return true;
+ }
+
+ /// @brief Clear history without reverting actions
+ void clear() {
+ actionPtr = 0;
+ actions.clear();
+ }
+
+ /// @brief Squash last n actions into one CombinedAction
+ /// @param n number of actions to squash
+ void squash(ptrdiff_t n) {
+ if (n < 2) {
+ return;
+ }
+ n = std::min(n, static_cast(actionPtr));
+ std::vector> squashing;
+ for (size_t i = actionPtr - n; i < actionPtr; i++) {
+ squashing.emplace_back(std::move(actions[i]));
+ }
+ actions.erase(actions.begin() + actionPtr - n, actions.end());
+ actionPtr -= n;
+ store(std::make_unique(std::move(squashing)));
+ }
+
+ size_t size() const {
+ return actionPtr;
+ }
+
+ /// @brief On destruction squashing actions stored since initialization
+ struct Combination {
+ ActionsHistory& history;
+ size_t historySize;
+
+ Combination(ActionsHistory& history)
+ : history(history), historySize(history.size()) {
+ }
+
+ Combination(const Combination&) = delete;
+
+ Combination(Combination&&) = default;
+
+ ~Combination() {
+ history.squash(history.size() - historySize);
+ }
+ };
+
+ Combination beginCombination() {
+ return Combination(*this);
+ }
+private:
+ std::vector> actions;
+ size_t actionPtr = 0;
+ bool lock = false;
+};
diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp
index 80d774be..5778ed98 100644
--- a/src/engine/Engine.cpp
+++ b/src/engine/Engine.cpp
@@ -163,6 +163,9 @@ void Engine::updateHotkeys() {
if (Events::jpressed(keycode::F2)) {
saveScreenshot();
}
+ if (Events::jpressed(keycode::F8)) {
+ gui->toggleDebug();
+ }
if (Events::jpressed(keycode::F11)) {
settings.display.fullscreen.toggle();
}
@@ -481,6 +484,10 @@ const Content* Engine::getContent() const {
return content.get();
}
+Content* Engine::getWriteableContent() {
+ return content.get();
+}
+
std::vector Engine::getAllContentPacks() {
auto packs = getContentPacks();
packs.insert(packs.begin(), ContentPack::createCore(paths));
diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp
index b05881f6..ccfabeed 100644
--- a/src/engine/Engine.hpp
+++ b/src/engine/Engine.hpp
@@ -151,6 +151,8 @@ public:
/// @brief Get current Content instance
const Content* getContent() const;
+ Content* getWriteableContent();
+
/// @brief Get selected content packs
std::vector& getContentPacks();
diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp
index 1684799f..48a699f1 100644
--- a/src/frontend/hud.cpp
+++ b/src/frontend/hud.cpp
@@ -262,7 +262,7 @@ void Hud::updateHotbarControl() {
}
}
-void Hud::updateWorldGenDebugVisualization() {
+void Hud::updateWorldGenDebug() {
auto& level = frontend.getLevel();
const auto& chunks = *player.chunks;
auto generator =
@@ -314,7 +314,9 @@ void Hud::update(bool visible) {
const auto& chunks = *player.chunks;
const auto& menu = gui.getMenu();
- debugPanel->setVisible(debug && visible);
+ debugPanel->setVisible(
+ debug && visible && !(inventoryOpen && inventoryView == nullptr)
+ );
if (!visible && inventoryOpen) {
closeInventory();
@@ -358,7 +360,7 @@ void Hud::update(bool visible) {
debugMinimap->setVisible(debug && showGeneratorMinimap);
if (debug && showGeneratorMinimap) {
- updateWorldGenDebugVisualization();
+ updateWorldGenDebug();
}
}
diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp
index a978e282..3522cd34 100644
--- a/src/frontend/hud.hpp
+++ b/src/frontend/hud.hpp
@@ -135,7 +135,7 @@ class Hud : public util::ObjectsKeeper {
void dropExchangeSlot();
void showExchangeSlot();
- void updateWorldGenDebugVisualization();
+ void updateWorldGenDebug();
public:
Hud(Engine& engine, LevelFrontend& frontend, Player& player);
~Hud();
diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp
index b7ccf5ff..73ac9be2 100644
--- a/src/graphics/core/Batch2D.cpp
+++ b/src/graphics/core/Batch2D.cpp
@@ -142,7 +142,7 @@ void Batch2D::rect(
bool flippedY,
glm::vec4 tint
) {
- if (index + 6*B2D_VERTEX_SIZE >= capacity) {
+ if (index + 6 * B2D_VERTEX_SIZE >= capacity) {
flush();
}
setPrimitive(DrawPrimitive::triangle);
@@ -230,6 +230,11 @@ void Batch2D::rect(
}
void Batch2D::lineRect(float x, float y, float w, float h) {
+ if (index + 8 * B2D_VERTEX_SIZE >= capacity) {
+ flush();
+ }
+ setPrimitive(DrawPrimitive::line);
+
vertex(x, y, 0.0f, 0.0f, color.r, color.g, color.b, color.a);
vertex(x, y+h, 0.0f, 1.0f, color.r, color.g, color.b, color.a);
diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp
index 5610f603..8db99d99 100644
--- a/src/graphics/core/Batch2D.hpp
+++ b/src/graphics/core/Batch2D.hpp
@@ -48,10 +48,14 @@ public:
void sprite(float x, float y, float w, float h, float skew, int atlasRes, int index, glm::vec4 tint);
void point(float x, float y, float r, float g, float b, float a);
- void setColor(glm::vec4 color) {
+ void setColor(const glm::vec4& color) {
this->color = color;
}
+ void setColor(int r, int g, int b, int a=255) {
+ this->color = glm::vec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
+ }
+
void resetColor() {
this->color = glm::vec4(1.0f);
}
diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp
index 2116fdd3..74a78932 100644
--- a/src/graphics/core/Font.cpp
+++ b/src/graphics/core/Font.cpp
@@ -38,11 +38,11 @@ bool Font::isPrintableChar(uint codepoint) const {
}
}
-int Font::calcWidth(const std::wstring& text, size_t length) const {
+int Font::calcWidth(std::wstring_view text, size_t length) const {
return calcWidth(text, 0, length);
}
-int Font::calcWidth(const std::wstring& text, size_t offset, size_t length) const {
+int Font::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
return std::min(text.length()-offset, length) * glyphInterval;
}
diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp
index 07fe0532..eabfbcc6 100644
--- a/src/graphics/core/Font.hpp
+++ b/src/graphics/core/Font.hpp
@@ -56,14 +56,14 @@ public:
/// @param text selected text
/// @param length max substring length (default: no limit)
/// @return pixel width of the substring
- int calcWidth(const std::wstring& text, size_t length=-1) const;
+ int calcWidth(std::wstring_view text, size_t length=-1) const;
/// @brief Calculate text width in pixels
/// @param text selected text
/// @param offset start of the substring
/// @param length max substring length
/// @return pixel width of the substring
- int calcWidth(const std::wstring& text, size_t offset, size_t length) const;
+ int calcWidth(std::wstring_view text, size_t offset, size_t length) const;
/// @brief Check if character is visible (non-whitespace)
/// @param codepoint character unicode codepoint
diff --git a/src/graphics/render/TextsRenderer.cpp b/src/graphics/render/TextsRenderer.cpp
index 32681ce5..283dacaa 100644
--- a/src/graphics/render/TextsRenderer.cpp
+++ b/src/graphics/render/TextsRenderer.cpp
@@ -10,6 +10,7 @@
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Shader.hpp"
#include "presets/NotePreset.hpp"
+#include "constants.hpp"
TextsRenderer::TextsRenderer(
Batch3D& batch, const Assets& assets, const Frustum& frustum
@@ -44,7 +45,7 @@ void TextsRenderer::renderNote(
}
opacity = preset.xrayOpacity;
}
- const auto& font = assets.require("normal");
+ const auto& font = assets.require(FONT_DEFAULT);
glm::vec3 xvec = note.getAxisX();
glm::vec3 yvec = note.getAxisY();
diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp
index 55dd8ceb..95425da4 100644
--- a/src/graphics/ui/GUI.cpp
+++ b/src/graphics/ui/GUI.cpp
@@ -11,14 +11,15 @@
#include "frontend/UiDocument.hpp"
#include "frontend/locale.hpp"
#include "graphics/core/Batch2D.hpp"
+#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Shader.hpp"
+#include "graphics/core/Font.hpp"
#include "graphics/core/DrawContext.hpp"
#include "window/Events.hpp"
#include "window/Window.hpp"
#include "window/input.hpp"
#include "window/Camera.hpp"
-#include
#include
#include
@@ -34,12 +35,13 @@ GUI::GUI()
menu = std::make_shared