From bc52202b01dcf72f8daf179d95fa72f201a029cf Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 21 Dec 2021 02:00:22 +0100 Subject: [PATCH 1/3] Console window progress --- deps/imgui | 2 +- src/client/component/console.cpp | 2 +- src/client/component/game_console.cpp | 142 ++++++++++-------- src/client/component/game_console.hpp | 6 + src/client/component/gui.cpp | 3 + src/client/component/gui_asset_list.cpp | 10 +- src/client/component/gui_console.cpp | 192 ++++++++++++++++++++++++ 7 files changed, 284 insertions(+), 73 deletions(-) create mode 100644 src/client/component/gui_console.cpp diff --git a/deps/imgui b/deps/imgui index f791ff33..e55dce84 160000 --- a/deps/imgui +++ b/deps/imgui @@ -1 +1 @@ -Subproject commit f791ff33702d55603d182b586a2850418ec49e3d +Subproject commit e55dce841b4a5a738ae41745b534248596df7f5e diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 50944cb1..05a5d125 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -31,7 +31,7 @@ namespace console while (true) { std::getline(std::cin, cmd); - command::execute(cmd.data(), false); + game_console::add(cmd.data(), false); } }); } diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 0bdcb3b3..97c9681d 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -69,7 +69,7 @@ namespace game_console matches.clear(); } - void print(const std::string& data) + void print(const std::string& data, bool print_ = true) { if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count)) { @@ -78,7 +78,10 @@ namespace game_console con.output.push_back(data); - printf("%s\n", data.data()); + if (print_) + { + printf("%s\n", data.data()); + } if (con.output.size() > 1024) { @@ -182,50 +185,6 @@ namespace game_console return false; } - void find_matches(std::string input, std::vector& suggestions, const bool exact) - { - input = utils::string::to_lower(input); - - for (const auto& dvar : dvars::dvar_list) - { - auto name = utils::string::to_lower(dvar); - if (match_compare(input, name, exact)) - { - suggestions.push_back(dvar); - } - - if (exact && suggestions.size() > 1) - { - return; - } - } - - if (suggestions.size() == 0 && game::Dvar_FindVar(input.data())) - { - suggestions.push_back(input.data()); - } - - game::cmd_function_s* cmd = (*game::cmd_functions); - while (cmd) - { - if (cmd->name) - { - std::string name = utils::string::to_lower(cmd->name); - - if (match_compare(input, name, exact)) - { - suggestions.push_back(cmd->name); - } - - if (exact && suggestions.size() > 1) - { - return; - } - } - cmd = cmd->next; - } - } - void draw_input() { con.globals.font_height = static_cast(console_font->pixelHeight); @@ -534,21 +493,16 @@ namespace game_console void execute(const char* cmd) { - const auto sv_running = game::Dvar_FindVar("sv_running")->current.enabled; - - if (sv_running) - { - std::string command = cmd; - - scheduler::once([command]() - { - scripting::notify(*game::levelEntityId, "console_command", {command}); - }, scheduler::pipeline::server); - } - game::Cbuf_AddText(0, utils::string::va("%s \n", cmd)); } + void add(const std::string& cmd, bool print_) + { + print(cmd, print_); + history.push_front(cmd); + game::Cbuf_AddText(0, utils::string::va("%s \n", cmd.data())); + } + bool console_key_event(const int localClientNum, const int key, const int down) { if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) @@ -678,6 +632,69 @@ namespace game_console return true; } + void find_matches(std::string input, std::vector& suggestions, const bool exact) + { + input = utils::string::to_lower(input); + + for (const auto& dvar : dvars::dvar_list) + { + auto name = utils::string::to_lower(dvar); + if (match_compare(input, name, exact)) + { + suggestions.push_back(dvar); + } + + if (exact && suggestions.size() > 1) + { + return; + } + } + + if (suggestions.size() == 0 && game::Dvar_FindVar(input.data())) + { + suggestions.push_back(input.data()); + } + + game::cmd_function_s* cmd = (*game::cmd_functions); + while (cmd) + { + if (cmd->name) + { + std::string name = utils::string::to_lower(cmd->name); + + if (match_compare(input, name, exact)) + { + suggestions.push_back(cmd->name); + } + + if (exact && suggestions.size() > 1) + { + return; + } + } + cmd = cmd->next; + } + } + + std::deque& get_output() + { + return con.output; + } + + std::deque& get_history() + { + return history; + } + + void clear_console() + { + clear(); + con.line_count = 0; + con.output.clear(); + history_index = -1; + history.clear(); + } + class component final : public component_interface { public: @@ -699,14 +716,7 @@ namespace game_console strncpy_s(con.globals.auto_complete_choice, "", 64); // add clear command - command::add("clear", [&]() - { - clear(); - con.line_count = 0; - con.output.clear(); - history_index = -1; - history.clear(); - }); + command::add("clear", clear_console); // add our dvars dvars::con_inputBoxColor = dvars::register_vec4( diff --git a/src/client/component/game_console.hpp b/src/client/component/game_console.hpp index 4a9bec7c..ebc8adab 100644 --- a/src/client/component/game_console.hpp +++ b/src/client/component/game_console.hpp @@ -16,5 +16,11 @@ namespace game_console bool console_char_event(int local_client_num, int key); bool console_key_event(int local_client_num, int key, int down); + void find_matches(std::string input, std::vector& suggestions, const bool exact); void execute(const char* cmd); + void clear_console(); + void add(const std::string& cmd, bool print_ = true); + + std::deque& get_output(); + std::deque& get_history(); } \ No newline at end of file diff --git a/src/client/component/gui.cpp b/src/client/component/gui.cpp index b20aa0cb..eed43ef0 100644 --- a/src/client/component/gui.cpp +++ b/src/client/component/gui.cpp @@ -113,12 +113,15 @@ namespace gui } }); + ImGui::ShowDemoWindow(); + if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Windows")) { ImGui::Checkbox("Asset list", &enabled_menus["asset_list"]); ImGui::Checkbox("Entity list", &enabled_menus["entity_list"]); + ImGui::Checkbox("Console", &enabled_menus["console"]); ImGui::EndMenu(); } diff --git a/src/client/component/gui_asset_list.cpp b/src/client/component/gui_asset_list.cpp index 19c7756f..4732068a 100644 --- a/src/client/component/gui_asset_list.cpp +++ b/src/client/component/gui_asset_list.cpp @@ -15,6 +15,9 @@ namespace asset_list { namespace { + bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT]; + std::string filter_buffer{}; + void enum_assets(const game::XAssetType type, const std::function& callback, const bool includeOverride) { game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) @@ -31,19 +34,16 @@ namespace asset_list return; } - static bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT]; - { ImGui::Begin("Asset list", &gui::enabled_menus["asset_list"]); - static char filter[0x200]{}; - ImGui::InputText("asset type", filter, IM_ARRAYSIZE(filter)); + ImGui::InputText("asset type", &filter_buffer); for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) { const auto name = game::g_assetNames[i]; const auto type = static_cast(i); - if (strstr(name, filter)) + if (strstr(name, filter_buffer.data())) { ImGui::Checkbox(name, &shown_assets[type]); } diff --git a/src/client/component/gui_console.cpp b/src/client/component/gui_console.cpp new file mode 100644 index 00000000..3495a53c --- /dev/null +++ b/src/client/component/gui_console.cpp @@ -0,0 +1,192 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "scheduler.hpp" +#include "command.hpp" +#include "game_console.hpp" +#include "gui.hpp" + +#include +#include +#include + +namespace gui_console +{ + namespace + { + bool auto_scroll = true; + int history_index = -1; + std::string input{}; + std::string filter{}; + std::vector matches{}; + + int input_text_edit(ImGuiInputTextCallbackData* data) + { + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + matches.clear(); + const std::string text = data->Buf; + + if (text.find(" ") != std::string::npos) + { + game_console::find_matches(text.substr(0, text.find(" ")), matches, true); + } + else + { + game_console::find_matches(text, matches, false); + } + + if (matches.size() == 1) + { + const auto match = matches[0].data(); + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, match, match + strlen(match)); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + const auto history = game_console::get_history(); + + 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; + } + + std::string get_filtered_text() + { + std::string text{}; + + const auto history = game_console::get_output(); + for (const auto& line : history) + { + if (strstr(line.data(), filter.data())) + { + text.append(line.data()); + text.append("\n"); + } + } + + if (text[text.size() - 1] == '\n') + { + text.pop_back(); + } + + return text; + } + + void on_frame() + { + auto* open = &gui::enabled_menus["console"]; + if (!*open) + { + return; + } + + auto filtered_text = get_filtered_text(); + static auto input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | + ImGuiInputTextFlags_CallbackHistory; + + ImGui::Begin("Console", open); + + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &auto_scroll); + ImGui::EndPopup(); + } + + if (ImGui::Button("Clear")) + { + game_console::clear_console(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Copy")) + { + utils::string::set_clipboard_data(filtered_text); + } + + ImGui::Separator(); + + if (ImGui::Button("Options")) + { + ImGui::OpenPopup("Options"); + } + + ImGui::SameLine(); + ImGui::InputText("Filter", &filter); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + ImGui::BeginChild("console_scroll", ImVec2(0, -footer_height_to_reserve), false); + + ImGui::Text(filtered_text.data(), ImVec2(-1, -1)); + + if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + { + ImGui::SetScrollHereY(1.0f); + } + + ImGui::EndChild(); + + if (ImGui::InputText("Input", &input, input_text_flags, input_text_edit)) + { + game_console::add(input.data()); + input.clear(); + ImGui::SetKeyboardFocusHere(-1); + } + + ImGui::End(); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gui::on_frame(on_frame); + } + }; +} + +REGISTER_COMPONENT(gui_console::component) From 5da497b61bc0a440fe06dbd65f5433a39377da43 Mon Sep 17 00:00:00 2001 From: fed <58637860+fedddddd@users.noreply.github.com> Date: Tue, 21 Dec 2021 15:21:50 +0100 Subject: [PATCH 2/3] More console progress --- src/client/component/game_console.cpp | 10 ++++++++-- src/client/component/gui.cpp | 8 ++++++-- src/client/component/gui.hpp | 1 + src/client/component/gui_asset_list.cpp | 7 +++---- src/client/component/gui_console.cpp | 16 +++++++++++++++- src/client/component/gui_entity_list.cpp | 16 ++++++++-------- src/common/utils/string.cpp | 5 +++++ src/common/utils/string.hpp | 2 ++ 8 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 97c9681d..381c95c1 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -498,9 +498,15 @@ namespace game_console void add(const std::string& cmd, bool print_) { - print(cmd, print_); + execute(cmd.data()); + history.push_front(cmd); - game::Cbuf_AddText(0, utils::string::va("%s \n", cmd.data())); + if (history.size() > 10) + { + history.erase(history.begin() + 10); + } + + print(cmd, print_); } bool console_key_event(const int localClientNum, const int key, const int down) diff --git a/src/client/component/gui.cpp b/src/client/component/gui.cpp index eed43ef0..48ba7b97 100644 --- a/src/client/component/gui.cpp +++ b/src/client/component/gui.cpp @@ -113,8 +113,6 @@ namespace gui } }); - ImGui::ShowDemoWindow(); - if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Windows")) @@ -242,6 +240,12 @@ namespace gui }); } + void copy_to_clipboard(const std::string& text) + { + utils::string::set_clipboard_data(text); + gui::notification("Text copied to clipboard", utils::string::va("\"%s\"", text.data())); + } + class component final : public component_interface { public: diff --git a/src/client/component/gui.hpp b/src/client/component/gui.hpp index 8d0e2e98..0665215b 100644 --- a/src/client/component/gui.hpp +++ b/src/client/component/gui.hpp @@ -19,4 +19,5 @@ namespace gui void on_frame(const std::function& callback); bool is_menu_open(const std::string& name); void notification(const std::string& title, const std::string& text, const std::chrono::milliseconds duration = 3s); + void copy_to_clipboard(const std::string& text); } \ No newline at end of file diff --git a/src/client/component/gui_asset_list.cpp b/src/client/component/gui_asset_list.cpp index 4732068a..7f77abe3 100644 --- a/src/client/component/gui_asset_list.cpp +++ b/src/client/component/gui_asset_list.cpp @@ -43,7 +43,7 @@ namespace asset_list const auto name = game::g_assetNames[i]; const auto type = static_cast(i); - if (strstr(name, filter_buffer.data())) + if (utils::string::find_lower(name, filter_buffer)) { ImGui::Checkbox(name, &shown_assets[type]); } @@ -73,15 +73,14 @@ namespace asset_list const auto asset = game::XAsset{type, header}; const auto* const asset_name = game::DB_GetXAssetName(&asset); - if (!strstr(asset_name, filter)) + if (!utils::string::find_lower(asset_name, filter)) { return; } if (ImGui::Button(asset_name)) { - utils::string::set_clipboard_data(asset_name); - gui::notification("Text copied to clipboard!", utils::string::va("\"%s\"", asset_name)); + gui::copy_to_clipboard(asset_name); } }, true); diff --git a/src/client/component/gui_console.cpp b/src/client/component/gui_console.cpp index 3495a53c..1ac281fb 100644 --- a/src/client/component/gui_console.cpp +++ b/src/client/component/gui_console.cpp @@ -99,7 +99,7 @@ namespace gui_console const auto history = game_console::get_output(); for (const auto& line : history) { - if (strstr(line.data(), filter.data())) + if (utils::string::find_lower(line, filter)) { text.append(line.data()); text.append("\n"); @@ -144,6 +144,7 @@ namespace gui_console if (ImGui::Button("Copy")) { utils::string::set_clipboard_data(filtered_text); + gui::notification("Console", "Text copied to clipboard"); } ImGui::Separator(); @@ -170,7 +171,20 @@ namespace gui_console if (ImGui::InputText("Input", &input, input_text_flags, input_text_edit)) { + auto history = game_console::get_history(); + + if (history_index != -1) + { + const auto itr = history.begin() + history_index; + + if (*itr == input) + { + history.erase(history.begin() + history_index); + } + } + game_console::add(input.data()); + input.clear(); ImGui::SetKeyboardFocusHere(-1); } diff --git a/src/client/component/gui_entity_list.cpp b/src/client/component/gui_entity_list.cpp index 246a2fe9..f1cae700 100644 --- a/src/client/component/gui_entity_list.cpp +++ b/src/client/component/gui_entity_list.cpp @@ -348,7 +348,8 @@ namespace entity_list const auto classname = classname_value.as(); const auto team_ = team_value.as(); - if (strstr(classname.data(), "actor_") && (team == entity_team::team_any || team_ == team_names[team])) + if (utils::string::find_lower(classname, "actor_") && + (team == entity_team::team_any || team_ == team_names[team])) { result.push(entity); } @@ -457,8 +458,8 @@ namespace entity_list for (const auto& filter : data.filters.fields) { if (field_value.is() && - strstr(field.first.data(), utils::string::to_lower(filter.first).data()) && - strstr(value_string.data(), filter.second.data())) + utils::string::find_lower(field.first, filter.first) && + utils::string::find_lower(value_string, filter.second)) { match_count++; } @@ -789,16 +790,14 @@ namespace entity_list if (ImGui::Button(field.first.data())) { - utils::string::set_clipboard_data(field.first); - gui::notification("Text copied to clipboard!", utils::string::va("\"%s\"", field.first.data())); + gui::copy_to_clipboard(field.first); } ImGui::SameLine(); if (ImGui::Button(field.second.data())) { - utils::string::set_clipboard_data(field.second); - gui::notification("Text copied to clipboard!", utils::string::va("\"%s\"", field.second.data())); + gui::copy_to_clipboard(field.second); } } @@ -815,7 +814,8 @@ namespace entity_list ImGui::InputText("field name", field_filter, IM_ARRAYSIZE(field_filter)); for (auto& field : data.selected_fields) { - if (strstr(field.first.data(), field_filter) && ImGui::Checkbox(field.first.data(), &field.second)) + if (utils::string::find_lower(field.first, field_filter) && + ImGui::Checkbox(field.first.data(), &field.second)) { data.force_update = true; } diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 3f90744c..546433db 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -192,4 +192,9 @@ namespace utils::string return str; } + + bool find_lower(const std::string& a, const std::string& b) + { + return to_lower(a).find(to_lower(b)) != std::string::npos; + } } diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 924e5441..9d6afb16 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -98,4 +98,6 @@ namespace utils::string std::wstring convert(const std::string& str); std::string replace(std::string str, const std::string& from, const std::string& to); + + bool find_lower(const std::string& a, const std::string& b); } From a39c18414c6cac50ee583617960c459d2247ca54 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 21 Dec 2021 15:43:51 +0100 Subject: [PATCH 3/3] Some fixes --- src/client/component/command.cpp | 2 +- src/client/component/gui_console.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index da034395..7c350c0a 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -58,7 +58,7 @@ namespace command const auto current = game::Dvar_ValueToString(dvar, nullptr, &dvar->current); const auto reset = game::Dvar_ValueToString(dvar, nullptr, &dvar->reset); - game_console::print(game_console::con_type_info, "\"%s\" is: \"%s\" default: \"%s\" hash: %i", + game_console::print(game_console::con_type_info, "\"%s\" is: \"%s\" default: \"%s\" hash: 0x%08lX", args[0], current, reset, dvar->name); game_console::print(game_console::con_type_info, " %s\n", diff --git a/src/client/component/gui_console.cpp b/src/client/component/gui_console.cpp index 1ac281fb..5bf2bf7b 100644 --- a/src/client/component/gui_console.cpp +++ b/src/client/component/gui_console.cpp @@ -41,7 +41,7 @@ namespace gui_console game_console::find_matches(text, matches, false); } - if (matches.size() == 1) + if (matches.size() < 24) { const auto match = matches[0].data(); data->DeleteChars(0, data->BufTextLen);