diff --git a/premake5.lua b/premake5.lua index 3b69b2c5..8e68c6f2 100644 --- a/premake5.lua +++ b/premake5.lua @@ -228,7 +228,7 @@ targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" configurations {"Debug", "Release"} language "C++" -cppdialect "C++20" +cppdialect "C++latest" architecture "x86_64" platforms "x64" diff --git a/src/client/component/gui/asset_list.cpp b/src/client/component/gui/asset_list.cpp index 5cb380d0..ae53b45d 100644 --- a/src/client/component/gui/asset_list.cpp +++ b/src/client/component/gui/asset_list.cpp @@ -20,17 +20,102 @@ namespace gui::asset_list bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT]{}; std::string asset_type_filter; std::string assets_name_filter[game::XAssetType::ASSET_TYPE_COUNT]; + std::string assets_value_filter[game::XAssetType::ASSET_TYPE_COUNT]; std::string zone_name_filter[game::XAssetType::ASSET_TYPE_COUNT]; std::unordered_map> asset_view_callbacks; - void render_window() + bool default_only[game::ASSET_TYPE_COUNT] = {}; + int asset_count[game::ASSET_TYPE_COUNT] = {}; + bool disabled_zones[game::ASSET_TYPE_COUNT][0x100] = {}; + bool show_asset_zone = true; + + void draw_table_row(game::XAssetType type, const game::XAssetEntry* entry, bool should_add_view_btn) { - static auto* enabled = &gui::enabled_menus["asset_list"]; - ImGui::Begin("Asset list", enabled); + const auto asset = entry->asset; + auto asset_name = game::DB_GetXAssetName(&asset); + const auto is_default = entry->zoneIndex == 0; + + if (asset_name[0] == '\0' || disabled_zones[type][entry->zoneIndex] || default_only[type] && !is_default) + { + return; + } + + if (is_default) + { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.43f, 0.15f, 0.15f, 1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.98f, 0.26f, 0.26f, 1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.98f, 0.06f, 0.06f, 1.f)); + } + + const auto _0 = gsl::finally([&] + { + if (is_default) + { + ImGui::PopStyleColor(3); + } + }); + + auto col_index = 0; + if (!utils::string::strstr_lower(asset_name, assets_name_filter[type].data())) + { + return; + } + + if (type == game::ASSET_TYPE_LOCALIZE_ENTRY) + { + if (!utils::string::strstr_lower(entry->asset.header.localize->value, assets_value_filter[type].data())) + { + return; + } + } + + ImGui::TableNextRow(); + + if (should_add_view_btn) + { + ImGui::TableSetColumnIndex(col_index++); + ImGui::PushID(asset_count[type]); + if (ImGui::Button("view")) + { + asset_view_callbacks.at(type)(asset_name); + } + ImGui::PopID(); + } - static bool show_asset_zone = true; + if (show_asset_zone) + { + ImGui::TableSetColumnIndex(col_index++); + if (entry->zoneIndex > 0) + { + ImGui::Text(game::g_zones[entry->zoneIndex].name); + } + else + { + ImGui::Text("default"); + } + } + + ImGui::TableSetColumnIndex(col_index++); + + if (ImGui::Button(asset_name)) + { + gui::copy_to_clipboard(asset_name); + } + if (type == game::ASSET_TYPE_LOCALIZE_ENTRY) + { + ImGui::TableSetColumnIndex(col_index++); + + if (ImGui::Button(entry->asset.header.localize->value)) + { + gui::copy_to_clipboard(entry->asset.header.localize->value); + } + } + } + + void draw_asset_type_list() + { if (ImGui::TreeNode("loaded zones")) { for (auto i = 1u; i <= *game::g_zoneCount; i++) @@ -60,115 +145,104 @@ namespace gui::asset_list } ImGui::EndChild(); - ImGui::End(); + } - for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) + void draw_asset_list_filter(const game::XAssetType type) + { + ImGui::Text("count: %i / %i", asset_count[type], game::g_poolSize[type]); + ImGui::InputText("name", &assets_name_filter[type]); + + if (type == game::ASSET_TYPE_LOCALIZE_ENTRY) { - const auto name = game::g_assetNames[i]; - const auto type = static_cast(i); + ImGui::InputText("value", &assets_value_filter[type]); + } - if (!shown_assets[type]) + if (ImGui::InputText("zone name", &zone_name_filter[type])) + { + for (auto zone = 0u; zone <= *game::g_zoneCount; zone++) { - continue; + const auto zone_name = game::g_zones[zone].name; + disabled_zones[type][zone] = !utils::string::strstr_lower(zone_name, zone_name_filter[type].data()); } + } - ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), ImVec2(1000, 1000)); - ImGui::Begin(name, &shown_assets[type]); + ImGui::Checkbox("default assets only", &default_only[type]); + } - static bool default_only[game::ASSET_TYPE_COUNT] = {}; - static int asset_count[game::ASSET_TYPE_COUNT] = {}; - static bool disabled_zones[game::ASSET_TYPE_COUNT][0x100] = {}; + constexpr auto get_table_flags() + { + constexpr auto flags = + ImGuiTableFlags_BordersInnerH | + ImGuiTableFlags_BordersOuterH | + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_BordersOuterV | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollX | + ImGuiTableFlags_ScrollY; + + return flags; + } - ImGui::Text("count: %i / %i", asset_count[type], game::g_poolSize[type]); + void draw_asset_list_entries(const game::XAssetType type) + { + const auto should_add_view_btn = asset_view_callbacks.contains(type); + constexpr auto flags = get_table_flags(); - ImGui::InputText("asset name", &assets_name_filter[type]); + asset_count[type] = 0; - if (ImGui::InputText("zone name", &zone_name_filter[type])) - { - for (auto zone = 0u; zone <= *game::g_zoneCount; zone++) - { - const auto zone_name = game::g_zones[zone].name; - disabled_zones[type][zone] = !utils::string::strstr_lower(zone_name, zone_name_filter[type].data()); - } - } - - const auto should_add_view_btn = asset_view_callbacks.contains(type); - ImGui::Checkbox("default assets only", &default_only[type]); + ImGui::BeginChild("assets list"); - ImGui::BeginChild("assets list"); + auto column_count = 1; + column_count += should_add_view_btn; + column_count += show_asset_zone; + column_count += type == game::ASSET_TYPE_LOCALIZE_ENTRY; - asset_count[type] = 0; + if (ImGui::BeginTable("assets", column_count, flags)) + { fastfiles::enum_asset_entries(type, [&](const game::XAssetEntry* entry) { asset_count[type]++; + draw_table_row(type, entry, should_add_view_btn); + }, true); - const auto asset = entry->asset; - auto asset_name = game::DB_GetXAssetName(&asset); - if (asset_name[0] == '\0') - { - return; - } + ImGui::EndTable(); + } - if (disabled_zones[type][entry->zoneIndex]) - { - return; - } + ImGui::EndChild(); + } - const auto is_default = entry->zoneIndex == 0; - if (default_only[type] && !is_default) - { - return; - } + void draw_asset_list(const game::XAssetType type) + { + if (!shown_assets[type]) + { + return; + } - if (is_default) - { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.43f, 0.15f, 0.15f, 1.f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.98f, 0.26f, 0.26f, 1.f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.98f, 0.06f, 0.06f, 1.f)); - } + const auto name = game::g_assetNames[type]; - if (utils::string::strstr_lower(asset_name, assets_name_filter[type].data())) - { - if (show_asset_zone) - { - if (entry->zoneIndex > 0) - { - ImGui::Text(game::g_zones[entry->zoneIndex].name); - } - else - { - ImGui::Text("default"); - } - - ImGui::SameLine(); - } - - if (ImGui::Button(asset_name)) - { - gui::copy_to_clipboard(asset_name); - } - - if (should_add_view_btn) - { - ImGui::SameLine(); - ImGui::PushID(asset_count[type]); - if (ImGui::Button("view")) - { - asset_view_callbacks.at(type)(asset_name); - } - ImGui::PopID(); - } - } + auto& io = ImGui::GetIO(); + ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), io.DisplaySize); + ImGui::Begin(name, &shown_assets[type]); - if (is_default) - { - ImGui::PopStyleColor(3); - } - }, true); + draw_asset_list_filter(type); + draw_asset_list_entries(type); + + ImGui::End(); + } + + void render_window() + { + static auto* enabled = &gui::enabled_menus["asset_list"]; + ImGui::Begin("Asset list", enabled); + + draw_asset_type_list(); - ImGui::EndChild(); - ImGui::End(); + for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) + { + draw_asset_list(static_cast(i)); } + + ImGui::End(); } } diff --git a/src/client/component/gui/asset_list.hpp b/src/client/component/gui/asset_list.hpp index 30a62e10..24b021fa 100644 --- a/src/client/component/gui/asset_list.hpp +++ b/src/client/component/gui/asset_list.hpp @@ -9,7 +9,7 @@ namespace gui::asset_list void add_view_button(int id, game::XAssetType type, const char* name); template - void add_asset_view(game::XAssetType type, const std::function& draw_callback) + void add_asset_view(game::XAssetType type, const std::function& draw_callback, ImVec2 min_size = ImVec2(0, 0)) { static std::unordered_set opened_assets; add_asset_view_callback(type, [](const std::string& name) @@ -29,6 +29,12 @@ namespace gui::asset_list continue; } + if (min_size.x != 0 && min_size.y != 0) + { + auto& io = ImGui::GetIO(); + ImGui::SetNextWindowSizeConstraints(min_size, ImVec2(io.DisplaySize.x, io.DisplaySize.y)); + } + auto is_open = true; if (ImGui::Begin(name.data(), &is_open)) { diff --git a/src/client/component/gui/assets/mapents.cpp b/src/client/component/gui/assets/mapents.cpp index 44de595b..35f0a1f1 100644 --- a/src/client/component/gui/assets/mapents.cpp +++ b/src/client/component/gui/assets/mapents.cpp @@ -33,8 +33,6 @@ namespace gui::asset_list::mapents std::atomic_bool done_parsing = false; }; - std::unordered_set temp_files; - utils::concurrency::container mapents; void parse_mapents(game::MapEnts* asset, mapents_t& data) @@ -89,7 +87,6 @@ namespace gui::asset_list::mapents { const auto current_path = std::filesystem::current_path().generic_string(); const std::string path = utils::string::va("%s\\h2-mod\\tmp\\%s", current_path.data(), data.asset->name); - temp_files.insert(path); utils::io::write_file(path, data.converted_mapents, false); ShellExecuteA(nullptr, nullptr, path.data(), nullptr, nullptr, SW_SHOWNORMAL); @@ -135,10 +132,7 @@ namespace gui::asset_list::mapents void pre_destroy() override { - for (const auto& file : temp_files) - { - utils::io::remove_file(file); - } + utils::io::remove_directory("h2-mod/tmp"); } }; } diff --git a/src/client/component/gui/assets/stringtable.cpp b/src/client/component/gui/assets/stringtable.cpp new file mode 100644 index 00000000..bac7ccee --- /dev/null +++ b/src/client/component/gui/assets/stringtable.cpp @@ -0,0 +1,138 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "component/scheduler.hpp" +#include "component/command.hpp" +#include "component/fastfiles.hpp" +#include "component/gsc/script_loading.hpp" +#include "../gui.hpp" +#include "../asset_list.hpp" + +#include "utils/mapents.hpp" + +#include +#include +#include +#include + +namespace gui::asset_list::stringtable +{ + namespace + { + void copy_table(game::StringTable* asset) + { + std::string buffer; + for (auto row = 0; row < asset->rowCount; row++) + { + for (auto column = 0; column < asset->columnCount; column++) + { + const auto index = (row * asset->columnCount) + column; + const auto string_value = asset->values[index].string; + const auto last_char = (column == asset->columnCount - 1) ? "\n" : ","; + + if (string_value == nullptr) + { + buffer.append(last_char); + } + else + { + std::string str = string_value; + auto added_quotes = false; + if (str.contains(',')) + { + added_quotes = true; + str.insert(str.begin(), '"'); + str.insert(str.end(), '"'); + } + + if (str.contains('\"') && !added_quotes) + { + str = std::regex_replace(str, std::regex("\""), "\\\""); + + str.insert(str.begin(), '"'); + str.insert(str.end(), '"'); + } + + str = std::regex_replace(str, std::regex("\n"), "\\n"); + buffer.append(utils::string::va("%s%s", str.data(), last_char)); + } + } + } + + gui::copy_to_clipboard(buffer); + } + + bool draw_asset(game::StringTable* asset) + { + if (asset->columnCount * asset->rowCount == 0) + { + ImGui::Text("empty table"); + return true; + } + + constexpr auto flags = + ImGuiTableFlags_BordersInnerH | + ImGuiTableFlags_BordersOuterH | + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_BordersOuterV | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollX | + ImGuiTableFlags_ScrollY; + + const auto size = ImGui::GetContentRegionAvail(); + + if (ImGui::Button("copy all")) + { + copy_table(asset); + } + + if (ImGui::BeginTable(asset->name, asset->columnCount, flags, size)) + { + for (auto row = 0; row < asset->rowCount; row++) + { + ImGui::TableNextRow(); + + for (auto column = 0; column < asset->columnCount; column++) + { + ImGui::TableSetColumnIndex(column); + const auto index = (row * asset->columnCount) + column; + const auto string_value = asset->values[index].string; + if (string_value == nullptr) + { + ImGui::Text(""); + } + else + { + ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushID(index); + if (ImGui::Button(string_value)) + { + gui::copy_to_clipboard(string_value); + } + ImGui::PopID(); + ImGui::PopStyleColor(); + } + } + } + + ImGui::EndTable(); + } + + return true; + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gui::asset_list::add_asset_view(game::ASSET_TYPE_STRINGTABLE, draw_asset, ImVec2(200, 200)); + } + }; +} + +REGISTER_COMPONENT(gui::asset_list::stringtable::component) diff --git a/src/client/component/gui/gui.cpp b/src/client/component/gui/gui.cpp index a384fe82..cb34710b 100644 --- a/src/client/component/gui/gui.cpp +++ b/src/client/component/gui/gui.cpp @@ -75,6 +75,63 @@ namespace gui }); } + std::vector imgui_colors = + { + ImGuiCol_FrameBg, + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBgActive, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_TextSelectedBg, + ImGuiCol_NavHighlight, + }; + + void update_colors() + { + auto& style = ImGui::GetStyle(); + const auto colors = style.Colors; + + const auto now = std::chrono::system_clock::now(); + const auto days = std::chrono::floor(now); + std::chrono::year_month_day y_m_d{days}; + + if (y_m_d.month() != std::chrono::month(6)) + { + return; + } + + for (const auto& id : imgui_colors) + { + const auto color = colors[id]; + + ImVec4 hsv_color = + { + static_cast((game::Sys_Milliseconds() / 100) % 256) / 255.f, + 1.f, 1.f, 1.f, + }; + + ImVec4 rgba_color{}; + ImGui::ColorConvertHSVtoRGB(hsv_color.x, hsv_color.y, hsv_color.z, rgba_color.x, rgba_color.y, rgba_color.z); + + rgba_color.w = color.w; + colors[id] = rgba_color; + } + } + void new_gui_frame() { ImGui::GetIO().MouseDrawCursor = toggled; @@ -87,6 +144,8 @@ namespace gui *game::keyCatchers &= ~0x10; } + update_colors(); + ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); run_event_queue();