diff --git a/src/client/component/gui.cpp b/src/client/component/gui.cpp index e45cd3c1..2ac770b2 100644 --- a/src/client/component/gui.cpp +++ b/src/client/component/gui.cpp @@ -120,6 +120,7 @@ namespace gui menu_checkbox("Asset list", "asset_list"); menu_checkbox("Entity list", "entity_list"); menu_checkbox("Console", "console"); + menu_checkbox("Script console", "script_console"); ImGui::EndMenu(); } diff --git a/src/client/component/gui_console.cpp b/src/client/component/gui_console.cpp index 22163bd5..b55ff3cd 100644 --- a/src/client/component/gui_console.cpp +++ b/src/client/component/gui_console.cpp @@ -182,10 +182,10 @@ namespace gui_console } } - game_console::add(input.data()); - - input.clear(); ImGui::SetKeyboardFocusHere(-1); + game_console::add(input.data()); + history_index = -1; + input.clear(); } ImGui::End(); diff --git a/src/client/component/gui_script_console.cpp b/src/client/component/gui_script_console.cpp new file mode 100644 index 00000000..ca4442ce --- /dev/null +++ b/src/client/component/gui_script_console.cpp @@ -0,0 +1,231 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "scheduler.hpp" +#include "command.hpp" +#include "gui.hpp" + +#include "game/scripting/lua/context.hpp" +#include "game/scripting/lua/engine.hpp" + +#include +#include +#include + +namespace gui_script_console +{ + namespace + { + bool auto_scroll = true; + bool multi_line_input = false; + int history_index = -1; + std::string input{}; + std::string filter{}; + std::deque history; + + struct menu_data_t + { + std::deque command_queue; + std::deque output; + }; + + utils::concurrency::container menu_data; + + std::string run_command(const std::string& code) + { + const auto result = scripting::lua::engine::load(code); + if (result.has_value()) + { + return result.value(); + } + else + { + return "Script not running"; + } + } + + void run_commands() + { + menu_data.access([](menu_data_t& menu_data_) + { + for (const auto& command : menu_data_.command_queue) + { + menu_data_.output.push_back(run_command(command)); + } + + menu_data_.command_queue.clear(); + }); + } + + int multi_line_input_text_edit(ImGuiInputTextCallbackData* data) + { + if (data->EventKey == ImGuiKey_Tab) + { + data->InsertChars(data->CursorPos, "\t"); + } + + return 0; + } + + int input_text_edit(ImGuiInputTextCallbackData* data) + { + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackHistory: + { + if (data->EventKey == ImGuiKey_UpArrow) + { + if (++history_index >= history.size()) + { + history_index = static_cast(history.size()) - 1; + } + + data->DeleteChars(0, data->BufTextLen); + + if (history_index != -1) + { + const auto text = history.at(history_index).data(); + data->InsertChars(0, text, text + strlen(text)); + } + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (--history_index < -1) + { + history_index = -1; + } + + data->DeleteChars(0, data->BufTextLen); + + if (history_index != -1) + { + const auto text = history.at(history_index).data(); + data->InsertChars(0, text, text + strlen(text)); + } + } + + break; + } + } + + return 0; + } + + void on_frame() + { + if (!gui::enabled_menus["script_console"]) + { + return; + } + + menu_data.access([](menu_data_t& menu_data_) + { + if (!game::CL_IsCgameInitialized()) + { + menu_data_.command_queue.clear(); + } + + static const float footer_height = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + static const auto input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | + ImGuiInputTextFlags_CallbackHistory; + + ImGui::Begin("Script console", &gui::enabled_menus["script_console"]); + + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &auto_scroll); + ImGui::Checkbox("Multi-line input", &multi_line_input); + ImGui::EndPopup(); + } + + if (ImGui::Button("Clear")) + { + menu_data_.output.clear(); + input.clear(); + history.clear(); + } + + ImGui::Separator(); + + if (ImGui::Button("Options")) + { + ImGui::OpenPopup("Options"); + } + + ImGui::SameLine(); + ImGui::InputText("Filter", &filter); + + ImGui::BeginChild("script_console_scroll", ImVec2(0, -1 * footer_height - 80.f * multi_line_input), false); + + for (const auto& line : menu_data_.output) + { + ImGui::Text(line.data()); + ImGui::Separator(); + } + + if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + { + ImGui::SetScrollHereY(1.0f); + } + + ImGui::EndChild(); + + bool execute = false; + if (multi_line_input) + { + ImGui::InputTextMultiline("", &input, ImVec2(0, 100), + ImGuiInputTextFlags_CallbackCompletion, multi_line_input_text_edit); + ImGui::SameLine(); + execute = ImGui::Button("Execute", ImVec2(100, 100)); + } + else + { + execute = ImGui::InputText("Input", &input, input_text_flags, input_text_edit); + } + + if (execute) + { + menu_data_.output.push_back(input); + menu_data_.command_queue.push_back(input); + + if (history_index != -1) + { + const auto itr = history.begin() + history_index; + + if (*itr == input) + { + history.erase(history.begin() + history_index); + } + } + + history.push_front(input); + if (history.size() > 10) + { + history.erase(history.begin() + 10); + } + + ImGui::SetKeyboardFocusHere(-1); + history_index = -1; + input.clear(); + } + + ImGui::End(); + }); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gui::on_frame(on_frame); + scheduler::loop(run_commands, scheduler::pipeline::server); + } + }; +} + +REGISTER_COMPONENT(gui_script_console::component) diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp index dbc054a1..3b670fd9 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -539,6 +539,53 @@ namespace scripting::lua this->load_script("__init__"); } + context::context() + : folder_({}) + , scheduler_(state_) + , event_handler_(state_) + + { + this->state_.open_libraries(sol::lib::base, + sol::lib::package, + sol::lib::io, + sol::lib::string, + sol::lib::os, + sol::lib::math, + sol::lib::table); + + this->state_["include"] = []() + { + }; + + this->state_["require"] = []() + { + }; + + setup_entity_type(this->state_, this->event_handler_, this->scheduler_); + } + + std::string context::load(const std::string& code) + { + try + { + const auto result = this->state_.safe_script(code, &sol::script_pass_on_error); + if (result.valid()) + { + const auto object = result.get(); + return this->state_["tostring"](object).get(); + } + else + { + const sol::error error = result; + return error.what(); + } + } + catch (const std::exception& e) + { + return e.what(); + } + } + context::~context() { this->state_.collect_garbage(); diff --git a/src/client/game/scripting/lua/context.hpp b/src/client/game/scripting/lua/context.hpp index dbf52472..e6175953 100644 --- a/src/client/game/scripting/lua/context.hpp +++ b/src/client/game/scripting/lua/context.hpp @@ -17,6 +17,7 @@ namespace scripting::lua class context { public: + context(); context(std::string folder); ~context(); @@ -29,6 +30,8 @@ namespace scripting::lua void run_frame(); void notify(const event& e); + std::string load(const std::string& code); + private: sol::state state_{}; std::string folder_; diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index 9d5d8edb..821d4ec4 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -19,6 +19,8 @@ namespace scripting::lua::engine void load_scripts() { + get_scripts().push_back(std::make_unique()); + const auto script_dir = "scripts/"s; if (!utils::io::directory_exists(script_dir)) @@ -66,4 +68,15 @@ namespace scripting::lua::engine script->run_frame(); } } + + std::optional load(const std::string& code) + { + if (get_scripts().size() == 0) + { + return {}; + } + + const auto& script = get_scripts()[0]; + return {script->load(code)}; + } } diff --git a/src/client/game/scripting/lua/engine.hpp b/src/client/game/scripting/lua/engine.hpp index 471316cd..bab42cdd 100644 --- a/src/client/game/scripting/lua/engine.hpp +++ b/src/client/game/scripting/lua/engine.hpp @@ -8,4 +8,6 @@ namespace scripting::lua::engine void stop(); void notify(const event& e); void run_frame(); + + std::optional load(const std::string& code); }