Skip to content

Commit

Permalink
CVars: Add homebrew console w/ autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed May 31, 2023
1 parent 3d0733b commit 7842daf
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 1 deletion.
46 changes: 46 additions & 0 deletions shared/sdk/ConsoleManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,52 @@ class FConsoleManager : public IConsoleManager {
return m_console_objects;
}

IConsoleObject* find(const std::wstring& name) {
// make lower
std::wstring lower_name = name;
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::towlower);

for (auto& element : m_console_objects) {
if (element.key != nullptr) {
// case insensitive compare
std::wstring lower_key = element.key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::towlower);

if (lower_key == lower_name) {
return element.value;
}
}
}

return nullptr;
}

std::vector<ConsoleObjectElement> fuzzy_find(const std::wstring& name) {
// make lower
std::wstring lower_name = name;
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::towlower);

std::vector<ConsoleObjectElement> results{};

for (auto& element : m_console_objects) {
if (element.key != nullptr & element.value != nullptr) {
// case insensitive compare
std::wstring lower_key = element.key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::towlower);

if (lower_key.find(lower_name) != std::wstring::npos) {
results.push_back(element);
}
}
}

std::sort(results.begin(), results.end(), [](const ConsoleObjectElement& a, const ConsoleObjectElement& b) -> bool {
return std::wstring{a.key} < std::wstring{b.key};
});

return results;
}

private:
ConsoleObjectArray m_console_objects;
};
Expand Down
2 changes: 2 additions & 0 deletions src/mods/VR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,8 @@ void VR::on_pre_imgui_frame() {
void VR::on_frame() {
ZoneScopedN(__FUNCTION__);

m_cvar_manager->on_frame();

if (!get_runtime()->ready()) {
return;
}
Expand Down
107 changes: 107 additions & 0 deletions src/mods/vr/CVarManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ void CVarManager::on_draw_ui() {

ImGui::TextWrapped("Frozen CVars: %i", frozen_cvars);

ImGui::Checkbox("Display Console", &m_wants_display_console);

if (ImGui::Button("Dump All CVars")) {
GameThreadWorker::get().enqueue([this]() {
dump_commands();
Expand Down Expand Up @@ -114,6 +116,12 @@ void CVarManager::on_draw_ui() {
}
}

void CVarManager::on_frame() {
if (m_wants_display_console) {
display_console();
}
}

void CVarManager::on_config_load(const utility::Config& cfg, bool set_defaults) {
ZoneScopedN(__FUNCTION__);

Expand Down Expand Up @@ -184,6 +192,105 @@ void CVarManager::dump_commands() {
}
}

// Use ImGui to display a homebrew console.
void CVarManager::display_console() {
bool open = true;

ImGui::SetNextWindowSize(ImVec2(720, 512), ImGuiCond_::ImGuiCond_Once);
if (ImGui::Begin("UEVRConsole", &open)) {
const auto console_manager = sdk::FConsoleManager::get();

if (console_manager == nullptr) {
ImGui::TextWrapped("Failed to get FConsoleManager.");
ImGui::End();
return;
}


ImGui::TextWrapped("Note: This is a homebrew console. It is not the same as the in-game console.");

ImGui::Separator();

ImGui::Text("> ");
ImGui::SameLine();

ImGui::PushItemWidth(-1);

// Do a preliminary parse of the input buffer to see if we can autocomplete.
{
const auto entire_command = std::string_view{ m_console.input_buffer.data() };

if (entire_command != m_console.last_parsed_buffer) {
std::vector<std::string> args{};

// Use getline
std::stringstream ss{ entire_command.data() };
while (ss.good()) {
std::string arg{};
std::getline(ss, arg, ' ');
args.push_back(arg);
}

m_console.last_autocomplete_string = "";

if (!args.empty()) {
const auto possible_commands = console_manager->fuzzy_find(utility::widen(args[0]));

for (const auto& command : possible_commands) {
m_console.last_autocomplete_string += utility::narrow(command.key) + "\n";
}
}

m_console.last_parsed_buffer = entire_command;
}
}

if (ImGui::InputText("##UEVRConsoleInput", m_console.input_buffer.data(), m_console.input_buffer.size(), ImGuiInputTextFlags_EnterReturnsTrue)) {
m_console.input_buffer[m_console.input_buffer.size() - 1] = '\0';

if (m_console.input_buffer[0] != '\0') {
const auto entire_command = std::string_view{ m_console.input_buffer.data() };

// Split the command into the arguments via ' ' (space).
std::vector<std::string> args{};

// Use getline
std::stringstream ss{ entire_command.data() };
while (ss.good()) {
std::string arg{};
std::getline(ss, arg, ' ');
args.push_back(arg);
}

// Execute the command.
if (!args.empty() && args.size() >= 2) {
auto object = console_manager->find(utility::widen(args[0]));

if (object != nullptr && object->AsCommand() == nullptr) {
auto var = (sdk::IConsoleVariable*)object;

GameThreadWorker::get().enqueue([var, value = utility::widen(args[1])]() {
var->Set(value.c_str());
});
}
}

m_console.history.push_back(m_console.input_buffer.data());
m_console.history_index = m_console.history.size();

m_console.input_buffer.fill('\0');
}
}

// Display autocomplete
if (!m_console.last_autocomplete_string.empty()) {
ImGui::TextUnformatted(m_console.last_autocomplete_string.c_str());
}

ImGui::End();
}
}

std::string CVarManager::CVar::get_key_name() {
ZoneScopedN(__FUNCTION__);

Expand Down
15 changes: 14 additions & 1 deletion src/mods/vr/CVarManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class CVarManager final : public ModComponent {

void on_pre_engine_tick(sdk::UGameEngine* engine, float delta) override;
void on_draw_ui() override;
void on_frame() override;
void display_console();
void on_config_load(const utility::Config& cfg, bool set_defaults) override;

void dump_commands();
Expand Down Expand Up @@ -150,7 +152,18 @@ class CVarManager final : public ModComponent {

private:
std::vector<std::shared_ptr<CVar>> m_displayed_cvars{};
std::vector<std::shared_ptr<CVar>> m_all_cvars{}; // ones the user can manually add to cvars.txt
std::vector<std::shared_ptr<CVar>> m_all_cvars{}; // ones the user can manually add to cvars.txt'

struct {
std::array<char, 256> input_buffer{};
std::vector<std::string> history{};
std::string last_parsed_name{};
std::string last_parsed_buffer{};
std::string last_autocomplete_string{};
size_t history_index{0};
} m_console;

bool m_wants_display_console{false};

static inline std::vector<std::shared_ptr<CVarStandard>> s_default_standard_cvars {
// Bools
Expand Down

0 comments on commit 7842daf

Please sign in to comment.