diff --git a/res/layouts/console.xml b/res/layouts/console.xml new file mode 100644 index 00000000..bbe869d0 --- /dev/null +++ b/res/layouts/console.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua new file mode 100644 index 00000000..7ed29c1f --- /dev/null +++ b/res/layouts/console.xml.lua @@ -0,0 +1,53 @@ +history = session.get_entry("commands_history") +history_pointer = #history + +function setup_variables() + local x,y,z = player.get_pos(hud.get_player()) + console.set('pos.x', x) + console.set('pos.y', y) + console.set('pos.z', z) +end + +function on_history_up() + if history_pointer == 0 then + return + end + document.prompt.text = history[history_pointer] + document.prompt.caret = -1 + history_pointer = history_pointer - 1 +end + +function on_history_down() + if history_pointer == #history-1 then + return + end + history_pointer = history_pointer + 1 + document.prompt.text = history[history_pointer + 1] + document.prompt.caret = -1 +end + +function add_to_history(text) + table.insert(history, text) + history_pointer = #history +end + +function submit(text) + add_to_history(text) + setup_variables() + + local status, result = pcall(function() return console.execute(text) end) + if result ~= nil then + local prevtext = document.log.text + if #prevtext == 0 then + document.log:paste(tostring(result)) + else + document.log:paste('\n'..tostring(result)) + end + end + document.prompt.text = "" + document.prompt.focused = true +end + +function on_open() + document.prompt.focused = true +end diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua new file mode 100644 index 00000000..48741102 --- /dev/null +++ b/res/scripts/stdcmd.lua @@ -0,0 +1,89 @@ +local SEPARATOR = "________________" +SEPARATOR = SEPARATOR..SEPARATOR..SEPARATOR + +function build_scheme(command) + local str = command.name.." " + for i,arg in ipairs(command.args) do + if arg.optional then + str = str.."["..arg.name.."] " + else + str = str.."<"..arg.name.."> " + end + end + return str +end + +console.add_command( + "help name:str=''", + "Show help infomation for the specified command", + function (args, kwargs) + local name = args[1] + if #name == 0 then + local commands = console.get_commands_list() + table.sort(commands) + local str = "Available commands:" + for i,k in ipairs(commands) do + str = str.."\n "..build_scheme(console.get_command_info(k)) + end + return str.."\nuse 'help '" + end + local command = console.get_command_info(name) + if command == nil then + return string.format("command %q not found", name) + end + local where = "" + local str = SEPARATOR.."\n"..command.description.."\n"..name.." " + for i,arg in ipairs(command.args) do + where = where.."\n "..arg.name.." - "..arg.type + if arg.optional then + str = str.."["..arg.name.."] " + where = where.." (optional)" + else + str = str.."<"..arg.name.."> " + end + end + if #command.args then + str = str.."\nwhere"..where + end + + return str.."\n"..SEPARATOR + end +) + +console.add_command( + "obj.tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z", + "Teleport object", + function (args, kwargs) + player.set_pos(unpack(args)) + end +) +console.add_command( + "echo value:str", + "Print value to the console", + function (args, kwargs) + return args[1] + end +) +console.add_command( + "time.set value:num", + "Set day time [0..1] where 0 is midnight, 0.5 is noon", + function (args, kwargs) + return world.set_day_time(args[1]) + end +) +console.add_command( + "blocks.fill id:str x:num~pos.x y:num~pos.y z:num~pos.z w:int h:int d:int", + "Fill specified zone with blocks", + function (args, kwargs) + local name, x, y, z, w, h, d = unpack(args) + local id = block.index(name) + for ly=0,h-1 do + for lz=0,d-1 do + for lx=0,w-1 do + block.set(x+lx, y+ly, z+lz, id) + end + end + end + return tostring(w*h*d).." blocks set" + end +) diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 94d6774b..979c7330 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -9,6 +9,7 @@ world.generators.default=Default world.generators.flat=Flat # Bindings +devtools.console=Console movement.forward=Forward movement.back=Back movement.left=Left diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 04ee4e69..ec2946ef 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -56,6 +56,7 @@ settings.UI Sounds=Звуки Интерфейса settings.V-Sync=Вертикальная Синхронизация # Управление +devtools.console=Консоль movement.forward=Вперёд movement.back=Назад movement.left=Влево diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index 50538a50..df59fcb3 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -154,8 +154,13 @@ void BasicParser::expectNewLine() { } } -void BasicParser::goBack() { - if (pos) pos--; +void BasicParser::goBack(size_t count) { + if (pos < count) { + throw std::runtime_error("pos < jump"); + } + if (pos) { + pos -= count; + } } char BasicParser::peek() { @@ -166,6 +171,13 @@ char BasicParser::peek() { return source[pos]; } +char BasicParser::peekNoJump() { + if (pos >= source.length()) { + throw error("unexpected end"); + } + return source[pos]; +} + std::string_view BasicParser::readUntil(char c) { int start = pos; while (hasNext() && source[pos] != c) { diff --git a/src/coders/commons.hpp b/src/coders/commons.hpp index c45d4f28..1584c92d 100644 --- a/src/coders/commons.hpp +++ b/src/coders/commons.hpp @@ -31,11 +31,11 @@ inline bool is_whitespace(int c) { } inline bool is_identifier_start(int c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '.'; + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '.'; } inline bool is_identifier_part(int c) { - return is_identifier_start(c) || is_digit(c); + return is_identifier_start(c) || is_digit(c) || c == '-'; } inline int hexchar2int(int c) { @@ -86,7 +86,7 @@ protected: void expect(const std::string& substring); bool isNext(const std::string& substring); void expectNewLine(); - void goBack(); + void goBack(size_t count=1); int64_t parseSimpleInt(int base); dynamic::Value parseNumber(int sign); @@ -99,6 +99,7 @@ public: std::string parseName(); bool hasNext(); char peek(); + char peekNoJump(); char nextChar(); BasicParser(std::string_view file, std::string_view source); diff --git a/src/coders/json.cpp b/src/coders/json.cpp index f6a7a19e..89f06354 100644 --- a/src/coders/json.cpp +++ b/src/coders/json.cpp @@ -36,14 +36,6 @@ inline void newline( } } -void stringify( - const Value& value, - std::stringstream& ss, - int indent, - const std::string& indentstr, - bool nice -); - void stringifyObj( const Map* obj, std::stringstream& ss, @@ -91,6 +83,8 @@ void stringifyValue( ss << *num; } else if (auto str = std::get_if(&value)) { ss << util::escape(*str); + } else { + ss << "null"; } } diff --git a/src/core_defs.cpp b/src/core_defs.cpp index cb6eb9c4..12e840b4 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -24,6 +24,7 @@ void corecontent::setup(ContentBuilder* builder) { } void corecontent::setup_bindings() { + Events::bind(BIND_DEVTOOLS_CONSOLE, inputtype::keyboard, keycode::GRAVE_ACCENT); Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W); Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S); Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D); diff --git a/src/core_defs.hpp b/src/core_defs.hpp index f071dd1d..1a2b0a9f 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -9,6 +9,7 @@ inline const std::string CORE_AIR = "core:air"; inline const std::string TEXTURE_NOTFOUND = "notfound"; // built-in bindings +inline const std::string BIND_DEVTOOLS_CONSOLE = "devtools.console"; inline const std::string BIND_MOVE_FORWARD = "movement.forward"; inline const std::string BIND_MOVE_BACK = "movement.back"; inline const std::string BIND_MOVE_LEFT = "movement.left"; diff --git a/src/data/dynamic.cpp b/src/data/dynamic.cpp index cbfe6a7b..4d60d2a5 100644 --- a/src/data/dynamic.cpp +++ b/src/data/dynamic.cpp @@ -242,6 +242,20 @@ size_t Map::size() const { return values.size(); } +static const std::string TYPE_NAMES[] { + "none", + "map", + "list", + "string", + "number", + "bool", + "integer", +}; + +const std::string& dynamic::type_name(const Value& value) { + return TYPE_NAMES[value.index()]; +} + List_sptr dynamic::create_list(std::initializer_list values) { return std::make_shared(values); } @@ -249,3 +263,19 @@ List_sptr dynamic::create_list(std::initializer_list values) { Map_sptr dynamic::create_map(std::initializer_list> entries) { return std::make_shared(entries); } + +number_t dynamic::get_number(const Value& value) { + if (auto num = std::get_if(&value)) { + return *num; + } else if (auto num = std::get_if(&value)) { + return *num; + } + throw std::runtime_error("cannot cast "+type_name(value)+" to number"); +} + +integer_t dynamic::get_integer(const Value& value) { + if (auto num = std::get_if(&value)) { + return *num; + } + throw std::runtime_error("cannot cast "+type_name(value)+" to integer"); +} diff --git a/src/data/dynamic.hpp b/src/data/dynamic.hpp index 39bb8fec..a9dc81c1 100644 --- a/src/data/dynamic.hpp +++ b/src/data/dynamic.hpp @@ -35,8 +35,16 @@ namespace dynamic { integer_t >; + const std::string& type_name(const Value& value); List_sptr create_list(std::initializer_list values={}); Map_sptr create_map(std::initializer_list> entries={}); + number_t get_number(const Value& value); + integer_t get_integer(const Value& value); + + inline bool is_numeric(const Value& value) { + return std::holds_alternative(value) || + std::holds_alternative(value); + } class List { public: @@ -125,6 +133,24 @@ namespace dynamic { Map& put(std::string key, std::unique_ptr value) { return put(key, List_sptr(value.release())); } + Map& put(std::string key, int value) { + return put(key, Value(static_cast(value))); + } + Map& put(std::string key, unsigned int value) { + return put(key, Value(static_cast(value))); + } + Map& put(std::string key, int64_t value) { + return put(key, Value(static_cast(value))); + } + Map& put(std::string key, float value) { + return put(key, Value(static_cast(value))); + } + Map& put(std::string key, double value) { + return put(key, Value(static_cast(value))); + } + Map& put(std::string key, bool value) { + return put(key, Value(static_cast(value))); + } Map& put(std::string key, const Value& value); void remove(const std::string& key); diff --git a/src/engine.cpp b/src/engine.cpp index 7fdbedc5..a903453e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -23,6 +23,7 @@ #include "graphics/core/Shader.hpp" #include "graphics/ui/GUI.hpp" #include "logic/EngineController.hpp" +#include "logic/CommandsInterpreter.hpp" #include "logic/scripting/scripting.hpp" #include "util/listutil.hpp" #include "util/platform.hpp" @@ -59,7 +60,8 @@ inline void create_channel(Engine* engine, std::string name, NumberSetting& sett } Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths) - : settings(settings), settingsHandler(settingsHandler), paths(paths) + : settings(settings), settingsHandler(settingsHandler), paths(paths), + interpreter(std::make_unique()) { corecontent::setup_bindings(); loadSettings(); @@ -198,6 +200,7 @@ Engine::~Engine() { } content.reset(); assets.reset(); + interpreter.reset(); gui.reset(); logger.info() << "gui finished"; audio::close(); @@ -211,6 +214,10 @@ EngineController* Engine::getController() { return controller.get(); } +cmd::CommandsInterpreter* Engine::getCommandsInterpreter() { + return interpreter.get(); +} + PacksManager Engine::createPacksManager(const fs::path& worldFolder) { PacksManager manager; manager.setSources({ diff --git a/src/engine.hpp b/src/engine.hpp index 331864ed..61066143 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -34,6 +34,10 @@ namespace gui { class GUI; } +namespace cmd { + class CommandsInterpreter; +} + class initialize_error : public std::runtime_error { public: initialize_error(const std::string& message) : std::runtime_error(message) {} @@ -52,6 +56,7 @@ class Engine : public util::ObjectsKeeper { std::queue postRunnables; std::recursive_mutex postRunnablesMutex; std::unique_ptr controller; + std::unique_ptr interpreter; std::vector basePacks {"base"}; uint64_t frame = 0; @@ -136,6 +141,7 @@ public: void saveScreenshot(); EngineController* getController(); + cmd::CommandsInterpreter* getCommandsInterpreter(); PacksManager createPacksManager(const fs::path& worldFolder); diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 5cf09589..b593f9e0 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -56,7 +56,7 @@ std::unique_ptr UiDocument::read(scriptenv penv, std::string name, f auto xmldoc = xml::parse(file.u8string(), text); auto env = penv == nullptr - ? scripting::get_root_environment() + ? scripting::create_doc_environment(scripting::get_root_environment(), name) : scripting::create_doc_environment(penv, name); gui::UiXmlReader reader(env); diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index c22d8fe5..95aa69f7 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -204,6 +204,9 @@ void Hud::processInput(bool visible) { setPause(true); } } + if (!pause && Events::active(BIND_DEVTOOLS_CONSOLE)) { + showOverlay(assets->getLayout("core:console"), false); + } if (!Window::isFocused() && !pause && !isInventoryOpen()) { setPause(true); } @@ -501,7 +504,7 @@ void Hud::updateElementsPosition(const Viewport& viewport) { )); } secondUI->setPos(glm::vec2( - glm::min(width/2-invwidth/2, width-caWidth-10-invwidth), + glm::min(width/2-invwidth/2, width-caWidth-(inventoryView ? 10 : 0)-invwidth), height/2-totalHeight/2 )); } diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index 06b805c2..4c89aeaa 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -27,7 +27,7 @@ void menus::create_version_label(Engine* engine) { auto gui = engine->getGUI(); auto text = ENGINE_VERSION_STRING+" debug build"; gui->add(guiutil::create( - "