From 9f79f6129c408e0881d372bdcf4458dec863f755 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Thu, 29 Dec 2022 15:28:02 +0100 Subject: [PATCH 01/11] rjson fix - createFX - loopfx logic - loopfx, loop_wait kvp on fx_origin - implement new createFX logic > (new kvp "fxloop" and "loop_wait") kvp "fxloop" "1" makes createFX generation use loopfx instead of createOneshotEffect eg: "maps\mp\_fx::loopfx("effect_1", (304.00, -140.00, 104.00), 50);" with "loop_wait" specifying the timeout, in this case "50" - add new kvp's to fx_origin description in iw3xradiant.def - fixed update check > html std::wchar to std::string conversion (rapidjson was throwing an assert on the IsArray() check) --- assets/bin/iw3xradiant.def | 7 ++- src/components/modules/effects.cpp | 80 +++++++++++++++++--------- src/components/modules/main_module.cpp | 19 +++++- src/ggui/entity.cpp | 11 ++++ 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/assets/bin/iw3xradiant.def b/assets/bin/iw3xradiant.def index fc110a5..b75b047 100644 --- a/assets/bin/iw3xradiant.def +++ b/assets/bin/iw3xradiant.def @@ -144,7 +144,12 @@ NO_STATIC_SHADOWS - the opaque surfaces will not cast static light map shadows */ /*QUAKED fx_origin (0.45 0.3 0.2) (-2 -2 -2) (2 2 2) -default:"fx" "iw3xradiant_def" +used for createFX generation and general effect handling. +default: "fx" "iw3xradiant_def" + +fx - effect definition +fxloop - make createFX gen. use loopfx instead of createOneshotEffect +loop_wait - time to wait until re-triggering */ diff --git a/src/components/modules/effects.cpp b/src/components/modules/effects.cpp index cb69a29..6300cba 100644 --- a/src/components/modules/effects.cpp +++ b/src/components/modules/effects.cpp @@ -554,6 +554,16 @@ namespace components // map of fx defs present on the map (used so we do not load the same def multiple times) std::unordered_map createfx_loaded_fx_defs; + // list of loopfx (fx_origins marked as looping), contains + //std::unordered_map createfx_looping_fx_defs; + struct loopfx_def_s + { + std::string def_num; + std::string pos_and_timeout; + }; + + std::vector createfx_looping_fx_defs; + // world orientations per prefab level std::vector createfx_prefab_stack_orientations; @@ -649,29 +659,22 @@ namespace components effect_num = itr->second; } - createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; - createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; - createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; - createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; - createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; - - - - - - - - - /*def << "\tlevel._effect[ \"effect_" << *effect_count << "\" ] = loadfx( \"" << fx_path << "\" );" << std::endl; - - createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << *effect_count << "\" );" << std::endl; - createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; - createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; - createfx << "\tent.v[ \"fxid\" ] = \"effect_" << *effect_count << "\";" << std::endl; - createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; - - *effect_count += 1;*/ + // check if fx_origin is set to looping and save for later + if (const char* timeout_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "loop_wait"); + timeout_str && entity_gui->has_key_with_value(owner, "loopfx", "1")) + { + createfx_looping_fx_defs.emplace_back(effect_num, + utils::va("%.2f, %.2f, %.2f, %s", out_orient.origin[0], out_orient.origin[1], out_orient.origin[2], timeout_str)); + } + else + { + createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; + createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; + createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; + createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; + createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; + } } else if (prefab && prefab->owner && prefab->owner->prefab && prefab->owner->firstActive && prefab->owner->firstActive->eclass && prefab->owner->firstActive->eclass->classtype & game::ECLASS_PREFAB) { @@ -700,6 +703,7 @@ namespace components { // reset global vars createfx_loaded_fx_defs.clear(); + createfx_looping_fx_defs.clear(); createfx_prefab_stack_level = 0; createfx_prefab_stack_orientations.clear(); createfx_prefab_stack_orientations.reserve(8); @@ -798,13 +802,22 @@ namespace components effect_num = itr->second; } - createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; - createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << owner->origin[0] << ", " << owner->origin[1] << ", " << owner->origin[2] << " );" << std::endl; - createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << world_angles[0] << ", " << world_angles[1] << ", " << world_angles[2] << " );" << std::endl; - createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; - createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; - + // check if fx_origin is set to looping and save for later + if (const char* timeout_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "loop_wait"); + timeout_str && entity_gui->has_key_with_value(owner, "loopfx", "1")) + { + createfx_looping_fx_defs.emplace_back(effect_num, + utils::va("%.2f, %.2f, %.2f, %s", owner->origin[0], owner->origin[1], owner->origin[2], timeout_str)); + } + else + { + createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; + createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << owner->origin[0] << ", " << owner->origin[1] << ", " << owner->origin[2] << " );" << std::endl; + createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << world_angles[0] << ", " << world_angles[1] << ", " << world_angles[2] << " );" << std::endl; + createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; + createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; + } } if (sb == game::g_active_brushes()) @@ -818,6 +831,17 @@ namespace components } } + if (!createfx_looping_fx_defs.empty()) + { + def << std::endl; + + for (const auto& loop : createfx_looping_fx_defs) + { + const auto split = utils::split(loop.pos_and_timeout, ','); + def << "\tmaps\\mp\\_fx::loopfx(\"effect_" << loop.def_num << "\", (" << split[0] << ", " << split[1] << ", " << split[2] << "), " << split[3] << ");" << std::endl; + } + } + def << "}" << std::endl; createfx << "}" << std::endl; diff --git a/src/components/modules/main_module.cpp b/src/components/modules/main_module.cpp index 30cb6b0..3d6dbd4 100644 --- a/src/components/modules/main_module.cpp +++ b/src/components/modules/main_module.cpp @@ -1,5 +1,6 @@ #include "std_include.hpp" #include +#include bool get_html(const std::string& url, std::wstring& header, std::wstring& hmtl) { @@ -43,15 +44,27 @@ DWORD WINAPI update_check(LPVOID) std::wstring header, html; get_html(url, header, html); - std::ranges::transform(html.begin(), html.end(), std::back_inserter(game::glob::gh_update_releases_json), [](wchar_t c) + /*std::ranges::transform(html.begin(), html.end(), std::back_inserter(game::glob::gh_update_releases_json), [](wchar_t c) { return (char)c; - }); + });*/ + + // chatgpt is awesome + std::wstring_convert> converter; + game::glob::gh_update_releases_json = converter.to_bytes(html); if (!game::glob::gh_update_releases_json.empty()) { rapidjson::Document doc; - doc.Parse(game::glob::gh_update_releases_json.c_str()); + + if (doc.Parse(game::glob::gh_update_releases_json.c_str()).HasParseError()) + { +#if DEBUG + auto err = doc.GetParseError(); + __debugbreak(); +#endif + return TRUE; + } if (!doc.Empty()) { diff --git a/src/ggui/entity.cpp b/src/ggui/entity.cpp index 99778bf..48c7c1a 100644 --- a/src/ggui/entity.cpp +++ b/src/ggui/entity.cpp @@ -48,6 +48,12 @@ namespace ggui { "targetname", "my_targetname" }, }; + const entity_dialog::template_kvp eclass_fx_templates[] = + { + { "loopfx", "1" }, + { "loop_wait", "5" }, + }; + const entity_dialog::template_kvp eclass_generic_templates[] = { { "target", "my_target" }, @@ -81,6 +87,11 @@ namespace ggui tkvp = eclass_misc_templates; *size_out = IM_ARRAYSIZE(eclass_misc_templates); } + else if (utils::string_equals(eent->eclass->name, "fx_origin")) + { + tkvp = eclass_fx_templates; + *size_out = IM_ARRAYSIZE(eclass_fx_templates); + } else { tkvp = eclass_generic_templates; From 3551458c0efeee277e5692fd0462dcdf69ad4926 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Wed, 22 Mar 2023 21:33:14 +0100 Subject: [PATCH 02/11] limit very frequent error toasts (per frame error messages) --- src/common/imgui/imgui_notify.hpp | 3 ++- src/ggui/console.cpp | 33 +++++++++++++++++++++++++++---- src/ggui/console.hpp | 2 ++ src/ggui/preferences.cpp | 10 +++++++++- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/common/imgui/imgui_notify.hpp b/src/common/imgui/imgui_notify.hpp index a2aed20..419b1a9 100644 --- a/src/common/imgui/imgui_notify.hpp +++ b/src/common/imgui/imgui_notify.hpp @@ -63,10 +63,11 @@ enum ImGuiToastPos_ class ImGuiToast { private: - ImGuiToastType type = ImGuiToastType_None; char title[NOTIFY_MAX_MSG_LENGTH]; char content[NOTIFY_MAX_MSG_LENGTH]; int dismiss_time = NOTIFY_DEFAULT_DISMISS; +public: + ImGuiToastType type = ImGuiToastType_None; uint64_t creation_time = 0; private: diff --git a/src/ggui/console.cpp b/src/ggui/console.cpp index 9c41527..7087f98 100644 --- a/src/ggui/console.cpp +++ b/src/ggui/console.cpp @@ -40,10 +40,35 @@ namespace ggui { utils::ltrim(t_line); - ImGuiToast toast(ImGuiToastType_Error, 3000); - toast.set_title("Error"); - toast.set_content(t_line.c_str()); - ImGui::InsertNotification(toast); + const auto& notifications = imgui::notifications; + const auto tick = GetTickCount64(); + + // check for very frequent error notifications + if (!notifications.empty()) + { + const auto last_msg = ¬ifications.back(); + + if (last_msg->type == ImGuiToastType_Error) + { + if (tick - 100 < last_msg->creation_time) + { + m_error_timeout_tick = last_msg->creation_time; + return; + } + } + } + + if (tick > m_error_timeout_tick + 1500) + { + const auto was_timeout_error = m_error_timeout_tick != 0; + + ImGuiToast toast(ImGuiToastType_Error, 1500); + toast.set_title(was_timeout_error ? "Very frequent Error (Every Frame)" : "Error"); + toast.set_content(t_line.c_str()); + ImGui::InsertNotification(toast); + + m_error_timeout_tick = 0; + } } } } diff --git a/src/ggui/console.hpp b/src/ggui/console.hpp index e57ec2e..7dc008a 100644 --- a/src/ggui/console.hpp +++ b/src/ggui/console.hpp @@ -20,6 +20,7 @@ namespace ggui bool m_scroll_to_bottom; ImVec2 m_post_inputbox_cursor; bool m_input_focused; + ULONGLONG m_error_timeout_tick; console_dialog() { @@ -38,6 +39,7 @@ namespace ggui m_scroll_to_bottom = true; m_post_inputbox_cursor = ImVec2(0.0f, 0.0f); m_input_focused = false; + m_error_timeout_tick = 0; } ~console_dialog() override; diff --git a/src/ggui/preferences.cpp b/src/ggui/preferences.cpp index 01cf6b3..9928b06 100644 --- a/src/ggui/preferences.cpp +++ b/src/ggui/preferences.cpp @@ -529,13 +529,21 @@ namespace ggui ImGui::InsertNotification(toast); } - + ImGui::SameLine(); if (ImGui::Button("Print Error")) { game::printf_to_console("[ERR] This is an error message!"); } + static bool frequent_toast_test = false; + imgui::Checkbox("Frequent Toast Test", &frequent_toast_test); + + if (frequent_toast_test) + { + game::printf_to_console("[ERR] Test Toast"); + } + //if (imgui::Button("Mesh Test")) //{ // struct GfxVertexBuffer From 0d03356a2ce967f9cb0ab975810e50e050c2609a Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Thu, 23 Mar 2023 22:14:34 +0100 Subject: [PATCH 03/11] createFX - loopsound logic - enable loopsound generation with kvp :: "is_sound" "1" - define sound with kvp :: "soundalias" "name" --- assets/bin/iw3xradiant.def | 8 ++- src/components/modules/effects.cpp | 97 +++++++++++++++++++++++------- src/ggui/entity.cpp | 2 + 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/assets/bin/iw3xradiant.def b/assets/bin/iw3xradiant.def index b75b047..ad248fb 100644 --- a/assets/bin/iw3xradiant.def +++ b/assets/bin/iw3xradiant.def @@ -146,10 +146,16 @@ NO_STATIC_SHADOWS - the opaque surfaces will not cast static light map shadows /*QUAKED fx_origin (0.45 0.3 0.2) (-2 -2 -2) (2 2 2) used for createFX generation and general effect handling. default: "fx" "iw3xradiant_def" +default: "loopfx" "0" +default: "loop_wait" "5" +default: "is_sound" "0" +default: "soundalias" "none" fx - effect definition fxloop - make createFX gen. use loopfx instead of createOneshotEffect -loop_wait - time to wait until re-triggering +loop_wait - time to wait until re-triggering (fxloop) +is_sound - make createFX gen. use createLoopSound, ignores fx settings if enabled (0/1) +soundalias - soundalias to use for createLoopSound */ diff --git a/src/components/modules/effects.cpp b/src/components/modules/effects.cpp index 6300cba..81c5e05 100644 --- a/src/components/modules/effects.cpp +++ b/src/components/modules/effects.cpp @@ -659,21 +659,48 @@ namespace components effect_num = itr->second; } - - // check if fx_origin is set to looping and save for later - if (const char* timeout_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "loop_wait"); - timeout_str && entity_gui->has_key_with_value(owner, "loopfx", "1")) + // is sound + if (entity_gui->has_key_with_value(owner, "is_sound", "1")) { - createfx_looping_fx_defs.emplace_back(effect_num, - utils::va("%.2f, %.2f, %.2f, %s", out_orient.origin[0], out_orient.origin[1], out_orient.origin[2], timeout_str)); + std::string soundalias_name = "UNSET"; + + if (const char* soundalias_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "soundalias"); soundalias_str) + { + soundalias_name = soundalias_str; + } + + createfx << "\tent = maps\\mp\\_utility::createLoopSound();" << std::endl; + createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; + createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; + createfx << "\tent.v[ \"soundalias\" ] = \"" << soundalias_name.c_str() << "\";" << std::endl << std::endl; } + + // is fx else { - createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; - createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; - createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; - createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; - createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; + std::string loopfx_timeout = "100"; + + if (const char* timeout_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "loop_wait"); timeout_str) + { + loopfx_timeout = timeout_str; + } + + // is looping (make oneshot looping by re-triggering them) + if (entity_gui->has_key_with_value(owner, "loopfx", "1")) + { + createfx_looping_fx_defs.emplace_back(effect_num, + utils::va("%.2f, %.2f, %.2f, %s", out_orient.origin[0], out_orient.origin[1], out_orient.origin[2], loopfx_timeout.c_str())); + } + + // is oneshot (either real oneshot or indefinitely looping) + else + { + createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; + createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; + createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; + createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; + createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; + } } } else if (prefab && prefab->owner && prefab->owner->prefab && prefab->owner->firstActive && prefab->owner->firstActive->eclass && prefab->owner->firstActive->eclass->classtype & game::ECLASS_PREFAB) @@ -803,20 +830,48 @@ namespace components } - // check if fx_origin is set to looping and save for later - if (const char* timeout_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "loop_wait"); - timeout_str && entity_gui->has_key_with_value(owner, "loopfx", "1")) + // is sound + if (entity_gui->has_key_with_value(owner, "is_sound", "1")) { - createfx_looping_fx_defs.emplace_back(effect_num, - utils::va("%.2f, %.2f, %.2f, %s", owner->origin[0], owner->origin[1], owner->origin[2], timeout_str)); + std::string soundalias_name = "UNSET"; + + if (const char* soundalias_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "soundalias"); soundalias_str) + { + soundalias_name = soundalias_str; + } + + createfx << "\tent = maps\\mp\\_utility::createLoopSound();" << std::endl; + createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << owner->origin[0] << ", " << owner->origin[1] << ", " << owner->origin[2] << " );" << std::endl; + createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << world_angles[0] << ", " << world_angles[1] << ", " << world_angles[2] << " );" << std::endl; + createfx << "\tent.v[ \"soundalias\" ] = \"" << soundalias_name.c_str() << "\";" << std::endl << std::endl; } + + // is fx else { - createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; - createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << owner->origin[0] << ", " << owner->origin[1] << ", " << owner->origin[2] << " );" << std::endl; - createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << world_angles[0] << ", " << world_angles[1] << ", " << world_angles[2] << " );" << std::endl; - createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; - createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; + std::string loopfx_timeout = "100"; + + if (const char* timeout_str = entity_gui->get_value_for_key_from_epairs(owner->epairs, "loop_wait"); timeout_str) + { + loopfx_timeout = timeout_str; + } + + // is looping (make oneshot looping by re-triggering them) + if (entity_gui->has_key_with_value(owner, "loopfx", "1")) + { + createfx_looping_fx_defs.emplace_back(effect_num, + utils::va("%.2f, %.2f, %.2f, %s", owner->origin[0], owner->origin[1], owner->origin[2], loopfx_timeout.c_str())); + } + + // is oneshot (either real oneshot or indefinitely looping) + else + { + createfx << "\tent = maps\\mp\\_utility::createOneshotEffect( \"effect_" << effect_num << "\" );" << std::endl; + createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << owner->origin[0] << ", " << owner->origin[1] << ", " << owner->origin[2] << " );" << std::endl; + createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << world_angles[0] << ", " << world_angles[1] << ", " << world_angles[2] << " );" << std::endl; + createfx << "\tent.v[ \"fxid\" ] = \"effect_" << effect_num << "\";" << std::endl; + createfx << "\tent.v[ \"delay\" ] = -15;" << std::endl << std::endl; + } } } diff --git a/src/ggui/entity.cpp b/src/ggui/entity.cpp index 48c7c1a..fde1461 100644 --- a/src/ggui/entity.cpp +++ b/src/ggui/entity.cpp @@ -52,6 +52,8 @@ namespace ggui { { "loopfx", "1" }, { "loop_wait", "5" }, + { "is_sound", "1" }, + { "soundalias", "emt_metal_rattle_ring" }, }; const entity_dialog::template_kvp eclass_generic_templates[] = From cbcd29d4c4a3fd8cab06fc184511dd7b081b2008 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Sat, 25 Mar 2023 10:53:42 +0100 Subject: [PATCH 04/11] createFX: fix createLoopSound function path --- src/components/modules/effects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modules/effects.cpp b/src/components/modules/effects.cpp index 81c5e05..f423b6d 100644 --- a/src/components/modules/effects.cpp +++ b/src/components/modules/effects.cpp @@ -669,7 +669,7 @@ namespace components soundalias_name = soundalias_str; } - createfx << "\tent = maps\\mp\\_utility::createLoopSound();" << std::endl; + createfx << "\tent = maps\\mp\\_createfx::createLoopSound();" << std::endl; createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << out_orient.origin[0] << ", " << out_orient.origin[1] << ", " << out_orient.origin[2] << " );" << std::endl; createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << out_angles[0] << ", " << out_angles[1] << ", " << out_angles[2] << " );" << std::endl; createfx << "\tent.v[ \"soundalias\" ] = \"" << soundalias_name.c_str() << "\";" << std::endl << std::endl; @@ -840,7 +840,7 @@ namespace components soundalias_name = soundalias_str; } - createfx << "\tent = maps\\mp\\_utility::createLoopSound();" << std::endl; + createfx << "\tent = maps\\mp\\_createfx::createLoopSound();" << std::endl; createfx << "\tent.v[ \"origin\" ] = ( " << std::fixed << std::setprecision(2) << owner->origin[0] << ", " << owner->origin[1] << ", " << owner->origin[2] << " );" << std::endl; createfx << "\tent.v[ \"angles\" ] = ( " << std::fixed << std::setprecision(2) << world_angles[0] << ", " << world_angles[1] << ", " << world_angles[2] << " );" << std::endl; createfx << "\tent.v[ \"soundalias\" ] = \"" << soundalias_name.c_str() << "\";" << std::endl << std::endl; From 337eb99fa6c582f141f7a95b40eb91bc5db4e40d Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Tue, 18 Apr 2023 20:06:12 +0200 Subject: [PATCH 05/11] default hotkeys: unbind PatchInspector (SHIFT + S) OG radiant does not clear the PatchInspector hWnd pointer upon closing the window. The hWnd pointer is boolean checked in multiple functions (like BeginDrag or OnSelect) to run PatchInspector related code. This leads to error messages upon selections until radiant is restarted. --- assets/bin/IW3xRadiant/hotkeys.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/bin/IW3xRadiant/hotkeys.ini b/assets/bin/IW3xRadiant/hotkeys.ini index 7ba0071..8886fb4 100644 --- a/assets/bin/IW3xRadiant/hotkeys.ini +++ b/assets/bin/IW3xRadiant/hotkeys.ini @@ -22,7 +22,7 @@ ExtrudeTerrainRow = +alt O RemoveTerrainRow = +shift +ctrl Q SplitPatch = +shift +ctrl X SurfaceInspector = S -PatchInspector = +shift S +PatchInspector = ApplyPatchCap = +shift +ctrl P TolerantWeld = +shift +ctrl J RedisperseVertices = +shift F From 84f7e802d52bae037277668e2a78dbb9bcac2593 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Thu, 18 May 2023 14:34:57 +0200 Subject: [PATCH 06/11] integrate layers into toolbox - better context menu controls #24 --- assets/bin/IW3xRadiant/images/layers.iwi | Bin 0 -> 32796 bytes src/components/modules/gui.cpp | 2 +- src/game/dvars.cpp | 1 + src/game/dvars.hpp | 1 + src/ggui/layers.cpp | 182 +++++++++++++---------- src/ggui/layers.hpp | 3 + src/ggui/preferences.cpp | 1 + src/ggui/toolbox.cpp | 85 +++++++++-- src/ggui/toolbox.hpp | 5 +- 9 files changed, 188 insertions(+), 92 deletions(-) create mode 100644 assets/bin/IW3xRadiant/images/layers.iwi diff --git a/assets/bin/IW3xRadiant/images/layers.iwi b/assets/bin/IW3xRadiant/images/layers.iwi new file mode 100644 index 0000000000000000000000000000000000000000..9ef52400816c7936ef76a117bba9964ac088b109 GIT binary patch literal 32796 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 G_x}M2dX(S* literal 0 HcmV?d00001 diff --git a/src/components/modules/gui.cpp b/src/components/modules/gui.cpp index 675cd10..9748128 100644 --- a/src/components/modules/gui.cpp +++ b/src/components/modules/gui.cpp @@ -911,7 +911,7 @@ namespace components /* name */ "gui_saved_state_toolbox_child", /* default */ 0, /* mins */ 0, - /* maxs */ 4, + /* maxs */ 5, /* flags */ game::dvar_flags::saved, /* desc */ "saved closed/opened/active state of window"); } diff --git a/src/game/dvars.cpp b/src/game/dvars.cpp index b8e13fe..9b61f93 100644 --- a/src/game/dvars.cpp +++ b/src/game/dvars.cpp @@ -34,6 +34,7 @@ namespace dvars game::dvar_s* gui_props_toolbox = nullptr; game::dvar_s* gui_toolbox_integrate_cam_toolbar = nullptr; game::dvar_s* gui_toolbox_integrate_filter = nullptr; + game::dvar_s* gui_toolbox_integrate_layers = nullptr; game::dvar_s* gui_use_new_surfinspector = nullptr; game::dvar_s* gui_use_new_vertedit_dialog = nullptr; game::dvar_s* gui_use_new_context_menu = nullptr; diff --git a/src/game/dvars.hpp b/src/game/dvars.hpp index 4e9a9b7..c9ba3d7 100644 --- a/src/game/dvars.hpp +++ b/src/game/dvars.hpp @@ -34,6 +34,7 @@ namespace dvars extern game::dvar_s* gui_props_toolbox; extern game::dvar_s* gui_toolbox_integrate_cam_toolbar; extern game::dvar_s* gui_toolbox_integrate_filter; + extern game::dvar_s* gui_toolbox_integrate_layers; extern game::dvar_s* gui_use_new_surfinspector; extern game::dvar_s* gui_use_new_vertedit_dialog; extern game::dvar_s* gui_use_new_context_menu; diff --git a/src/ggui/layers.cpp b/src/ggui/layers.cpp index 568ee34..3e15118 100644 --- a/src/ggui/layers.cpp +++ b/src/ggui/layers.cpp @@ -20,13 +20,13 @@ namespace ggui const clayer_s* context_layer = nullptr; const clayer_s* context_layer_last_hovered = nullptr; - void do_context_menu() + void layer_dialog::do_context_menu() { - if(layer_context_open || layer_context_pending) + if (layer_context_open || layer_context_pending) { ggui::context_menu_style_begin(); - if(!ImGui::IsKeyPressed(ImGuiKey_Escape) && ImGui::BeginPopupContextItem("context_menu##camera")) + if (!imgui::IsKeyPressed(ImGuiKey_Escape) && imgui::BeginPopupContextItem("context_menu##layerdlg")) { // select layer in og layerwindow TreeView_SelectItem(game::layer_dlg->tree.m_hWnd, context_layer); @@ -34,56 +34,56 @@ namespace ggui layer_context_open = true; layer_context_pending = false; - if (ImGui::MenuItem("Add new layer as child")) + if (imgui::MenuItem("Add new layer as child")) { // CLayers::NewLayer utils::hook::call(0x41C690)(game::layer_dlg); } - if (ImGui::MenuItem("Set as active layer")) + if (imgui::MenuItem("Set as active layer")) { // CLayers::SetActiveLayer utils::hook::call(0x41C610)(game::layer_dlg); } - ImGui::Separator(); + imgui::Separator(); // same logic as CXYWnd::OnSelectionAddToActiveLayer (grid context menu) - if (ImGui::MenuItem("Add selection to layer")) + if (imgui::MenuItem("Add selection to layer")) { // CLayersDlg::AsignSelectionToLayer utils::hook::call(0x41C850)(game::layer_dlg); } - if (ImGui::MenuItem("Select everything within layer")) + if (imgui::MenuItem("Select everything within layer")) { // CLayersDlg::SelectAllBrushesInLayer utils::hook::call(0x41C960)(game::layer_dlg); } - ImGui::Separator(); + imgui::Separator(); - if (ImGui::MenuItem("Reset layer and children")) + if (imgui::MenuItem("Reset layer and children")) { // CLayersDlg::OnShowLayerAndChildren utils::hook::call(0x41CAF0)(game::layer_dlg); } - ImGui::Separator(); + imgui::Separator(); - if (ImGui::MenuItem("Rename layer")) + if (imgui::MenuItem("Rename layer")) { // CLayersDlg::RenameLayer utils::hook::call(0x41CC60)(game::layer_dlg); } - if (ImGui::MenuItem("Delete layer")) + if (imgui::MenuItem("Delete layer")) { // CLayersDlg::DeleteLayer utils::hook::call(0x41CBE0)(game::layer_dlg); } - ImGui::EndPopup(); + imgui::EndPopup(); } else { @@ -99,7 +99,7 @@ namespace ggui { context_layer_last_hovered = layer; - if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && !layer_context_pending) + if (imgui::IsMouseReleased(ImGuiMouseButton_Right) && !layer_context_pending) { layer_context_pending = true; context_layer = layer; @@ -108,32 +108,32 @@ namespace ggui void do_layer(const clayer_s* layer, bool is_sibling = false) { - if(!layer) + if (!layer) { return; } - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); // adds height to rows + imgui::TableNextRow(); + imgui::TableSetColumnIndex(0); + imgui::AlignTextToFramePadding(); // adds height to rows int pushed_colors = 0; const bool is_active_layer = layer == game::layer_dlg->active_layer; const bool is_hidden = layer->enabled == 32; const bool is_frozen = layer->enabled == 48; - if(is_active_layer) + if (is_active_layer) { - //ImGui::PushFontFromIndex(E_FONT::BOLD_18PX); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 1.0f, 0.6f, 1.0f)); pushed_colors++; + //imgui::PushFontFromIndex(E_FONT::BOLD_18PX); + imgui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 1.0f, 0.6f, 1.0f)); pushed_colors++; } else if (is_hidden) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); pushed_colors++; + imgui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); pushed_colors++; } else if (is_frozen) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f)); pushed_colors++; + imgui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f)); pushed_colors++; } const std::string layer_string_std = get_layer_name(layer->layer_string); @@ -151,19 +151,19 @@ namespace ggui { if (is_active_layer) { - ImGui::PopStyleColor(pushed_colors); + imgui::PopStyleColor(pushed_colors); pushed_colors = 0; } // ---- - ImGui::TableSetColumnIndex(1); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 12.0f); + imgui::TableSetColumnIndex(1); + imgui::SetCursorPosX(imgui::GetCursorPosX() + 12.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); - const ImVec2 btn_size = ImVec2(ImGui::GetFrameHeight() + 6.0f, ImGui::GetFrameHeight()); + imgui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); + const ImVec2 btn_size = ImVec2(imgui::GetFrameHeight() + 6.0f, imgui::GetFrameHeight()); - if (ImGui::Button(is_hidden ? ICON_FA_EYE_SLASH : ICON_FA_EYE, btn_size)) + if (imgui::Button(is_hidden ? ICON_FA_EYE_SLASH : ICON_FA_EYE, btn_size)) { if (is_hidden) { @@ -190,9 +190,14 @@ namespace ggui } } - ImGui::SameLine(0.0f, 6.0f); + if (imgui::IsItemHovered(ImGuiHoveredFlags_None)) + { + handle_context_menu(layer); + } + + imgui::SameLine(0.0f, 6.0f); - if (ImGui::Button(ICON_FA_SNOWFLAKE, btn_size)) + if (imgui::Button(ICON_FA_SNOWFLAKE, btn_size)) { if (is_frozen) { @@ -218,29 +223,34 @@ namespace ggui } } - ImGui::PopStyleVar(); // ImGuiStyleVar_FramePadding + if (imgui::IsItemHovered(ImGuiHoveredFlags_None)) + { + handle_context_menu(layer); + } + + imgui::PopStyleVar(); // ImGuiStyleVar_FramePadding if (pushed_colors) { - ImGui::PopStyleColor(pushed_colors); + imgui::PopStyleColor(pushed_colors); pushed_colors = 0; } }; // current layer (custom TreeNodeEx that returns hovered and pressed state - for context menu and bridging to og layerwindow) - if (ImGui::TreeNodeEx(layer_string, &is_hovered, &is_pressed, ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth | (is_last_node ? (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet) : 0))) + if (imgui::TreeNodeEx(layer_string, &is_hovered, &is_pressed, ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth | (is_last_node ? (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet) : 0))) { do_hide_and_freeze(); // ---- // do all its child layers child siblings - if(layer->child_layers) + if (layer->child_layers) { do_layer(layer->child_layers, false); } - ImGui::TreePop(); + imgui::TreePop(); } else // if node is collapsed { @@ -249,7 +259,7 @@ namespace ggui if (pushed_colors) { - ImGui::PopStyleColor(pushed_colors); + imgui::PopStyleColor(pushed_colors); pushed_colors = 0; } @@ -265,7 +275,7 @@ namespace ggui handle_context_menu(layer); //game::printf_to_console("Hovered %s", get_layer_name(layer->layer_string).c_str()); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + if (imgui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { TreeView_SelectItem(game::layer_dlg->tree.m_hWnd, layer); @@ -275,7 +285,7 @@ namespace ggui } // do all siblings for a child - if(!is_sibling) + if (!is_sibling) { for (auto sibling = layer->sibling_layer_same_level; sibling; sibling = sibling->sibling_layer_same_level) { @@ -284,60 +294,82 @@ namespace ggui } } - bool layer_dialog::gui() + void layer_dialog::do_table() { - ImGui::SetNextWindowSize(ImVec2(400.0f, 600.0f), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowPos(ggui::get_initial_window_pos(), ImGuiCond_FirstUseEver); - - if (ImGui::Begin("Layers##window", this->get_p_open(), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_HorizontalScrollbar)) + if (imgui::BeginTable("split", 2, ImGuiTableFlags_ScrollX | ImGuiTableFlags_NoSavedSettings /*| ImGuiTableFlags_Borders*/)) { - SPACING(0.0f, 2.0f); + imgui::TableSetupColumn("##cell_layer_desc", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHide, 240.0f); + imgui::TableSetupColumn("##cell_buttons", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHide, 90.0f); - if (ImGui::BeginTable("split", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_ScrollX)) - { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); + imgui::TableNextRow(); + imgui::TableSetColumnIndex(0); + //imgui::AlignTextToFramePadding(); - ImGui::PushFontFromIndex(BOLD_18PX); - ImGui::Text(" The Map"); - ImGui::PopFont(); - - //if (ImGui::TreeNodeEx(get_layer_name(game::layer_dlg->root_node_the_map->layer_string).c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanFullWidth)) + //if (imgui::TreeNodeEx(get_layer_name(game::layer_dlg->root_node_the_map->layer_string).c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanFullWidth)) + { + // do all siblings of the first child (same level as the first use-able layer) + for (auto child = game::layer_dlg->root_node_the_map->child_layers; child; child = child->sibling_layer_same_level) { - // do all siblings of the first child (same level as the first use-able layer) - for (auto child = game::layer_dlg->root_node_the_map->child_layers; child; child = child->sibling_layer_same_level) - { - do_layer(child, true); - } - - //ImGui::TreePop(); + do_layer(child, true); } - - ImGui::EndTable(); + + //imgui::TreePop(); } - do_context_menu(); + imgui::EndTable(); + } + } - ImGui::End(); - return true; + bool layer_dialog::gui() + { + if (dvars::gui_toolbox_integrate_layers && dvars::gui_toolbox_integrate_layers->current.enabled) + { + return false; + } + + imgui::SetNextWindowSize(ImVec2(400.0f, 600.0f), ImGuiCond_FirstUseEver); + imgui::SetNextWindowPos(ggui::get_initial_window_pos(), ImGuiCond_FirstUseEver); + //imgui::PushStyleColor(ImGuiCol_TableBorderStrong, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + + if (!imgui::Begin("Layers##window", this->get_p_open(), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_HorizontalScrollbar)) + { + //imgui::PopStyleColor(); + imgui::End(); + return false; } - return false; + SPACING(0.0f, 2.0f); + + do_table(); + do_context_menu(); + + //imgui::PopStyleColor(); + imgui::End(); + return true; } // CMainFrame::OnLayersDlg void layer_dialog::on_layerdialog_command() { - const auto gui = GET_GUI(ggui::layer_dialog); - - if (gui->is_inactive_tab() && gui->is_active()) + if (dvars::gui_toolbox_integrate_layers && dvars::gui_toolbox_integrate_layers->current.enabled) { - gui->set_bring_to_front(true); - return; + const auto tb = GET_GUI(ggui::toolbox_dialog); + + tb->set_bring_to_front(true); + tb->focus_child(toolbox_dialog::TB_CHILD::LAYERS); + tb->open(); } + else + { + const auto gui = GET_GUI(ggui::layer_dialog); + if (gui->is_inactive_tab() && gui->is_active()) + { + gui->set_bring_to_front(true); + return; + } - gui->toggle(); + gui->toggle(); + } } void layer_dialog::on_open() diff --git a/src/ggui/layers.hpp b/src/ggui/layers.hpp index 27883d6..b546fb0 100644 --- a/src/ggui/layers.hpp +++ b/src/ggui/layers.hpp @@ -11,6 +11,9 @@ namespace ggui // * // public member functions + void do_table(); + void do_context_menu(); + bool gui() override; void on_open() override; void on_close() override; diff --git a/src/ggui/preferences.cpp b/src/ggui/preferences.cpp index 9928b06..70900e7 100644 --- a/src/ggui/preferences.cpp +++ b/src/ggui/preferences.cpp @@ -105,6 +105,7 @@ namespace ggui ImGui::Checkbox("Integrate camera toolbar into toolbox", &dvars::gui_toolbox_integrate_cam_toolbar->current.enabled); TT(dvars::gui_toolbox_integrate_cam_toolbar->description); ImGui::Checkbox("Integrate entity-properties into toolbox", &dvars::gui_props_toolbox->current.enabled); TT(dvars::gui_props_toolbox->description); ImGui::Checkbox("Integrate filter window into toolbox", &dvars::gui_toolbox_integrate_filter->current.enabled); TT(dvars::gui_toolbox_integrate_filter->description); + ImGui::Checkbox("Integrate layer window into toolbox", &dvars::gui_toolbox_integrate_layers->current.enabled); TT(dvars::gui_toolbox_integrate_layers->description); const char* incorp_surf_inspector_strings[4] = { "None", "Entity Properties", "Toolbox" }; if (ImGui::SliderInt("Integrate surface-inspector into", &dvars::gui_props_surfinspector->current.integer, 0, 2, incorp_surf_inspector_strings[dvars::gui_props_surfinspector->current.integer])) diff --git a/src/ggui/toolbox.cpp b/src/ggui/toolbox.cpp index 78e75a4..dc2eb82 100644 --- a/src/ggui/toolbox.cpp +++ b/src/ggui/toolbox.cpp @@ -1195,19 +1195,14 @@ namespace ggui int style_colors = 0; int style_vars = 0; - ImGui::PushID("entity_properties"); + imgui::PushID("entity_properties"); setup_child(); { - const ImVec4 toolbar_button_background_active = ImVec4(0.20f, 0.20f, 0.20f, 1.0f); - const ImVec4 toolbar_button_background_hovered = ImVec4(0.225f, 0.225f, 0.225f, 1.0f); - const ImVec2 toolbar_button_size = ImVec2(32.0f, 32.0f); - const auto ent = GET_GUI(ggui::entity_dialog); - static float max_widget_width = 251.0f; // assumed first value - depends on total width of curve patch creation widget - + if (treenode_begin("Properties", true, style_colors, style_vars)) { - ImGui::SetNextItemWidth(-1); + imgui::SetNextItemWidth(-1); ent->draw_entprops(ImGui::CalcItemWidth() - 8.0f, 8.0f); treenode_end(style_colors, style_vars); @@ -1222,7 +1217,7 @@ namespace ggui if (treenode_begin("Comments", false, style_colors, style_vars)) { - ImGui::SetNextItemWidth(-1); + imgui::SetNextItemWidth(-1); ent->draw_comments(8.0f); treenode_end(style_colors, style_vars); @@ -1230,7 +1225,7 @@ namespace ggui SPACING(0.0f, 4.0f); } - ImGui::PopID(); + imgui::PopID(); } void toolbox_dialog::child_filter() @@ -1238,7 +1233,7 @@ namespace ggui int style_colors = 0; int style_vars = 0; - ImGui::PushID("filter"); + imgui::PushID("filter"); setup_child(); { const auto gui = GET_GUI(ggui::filter_dialog); @@ -1269,7 +1264,32 @@ namespace ggui SPACING(0.0f, 4.0f); } - ImGui::PopID(); + imgui::PopID(); + } + + void toolbox_dialog::child_layers() + { + imgui::PushID("layers"); + imgui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.125f)); + + { + const auto window = imgui::GetCurrentWindow(); + + const auto child_size = imgui::GetContentRegionAvail(); + const float window_height = window->ContentSize.y > window->SizeFull.y ? window->ContentSize.y : window->SizeFull.y; + + const auto min = imgui::GetCursorScreenPos(); + const auto max = ImVec2(min.x + child_size.x, min.y + window_height); + imgui::GetWindowDrawList()->AddRect(min, max, imgui::ColorConvertFloat4ToU32(ImVec4(0.1f, 0.1f, 0.1f, 1.0f)), 0.0f, ImDrawFlags_RoundCornersBottom); + imgui::Indent(4.0f); + } + + const auto gui = GET_GUI(ggui::layer_dialog); + gui->do_table(); + gui->do_context_menu(); + + imgui::PopStyleColor(); + imgui::PopID(); } bool toolbox_dialog::gui() @@ -1342,7 +1362,7 @@ namespace ggui mins = ImGui::GetCursorScreenPos(); static bool hov_patch; - if(tb->image_togglebutton("toolbox_patch" + if (tb->image_togglebutton("toolbox_patch" , hov_patch , m_child_current == static_cast(_toolbox_childs[CAT_PATCH].index) , "Patches" @@ -1432,7 +1452,34 @@ namespace ggui // # - if(dvars::gui_toolbox_integrate_cam_toolbar && dvars::gui_toolbox_integrate_cam_toolbar->current.enabled) + mins = ImGui::GetCursorScreenPos(); + + if (dvars::gui_toolbox_integrate_layers && dvars::gui_toolbox_integrate_layers->current.enabled) + { + static bool hov_layers; + if (tb->image_togglebutton("layers" + , hov_layers + , m_child_current == static_cast(_toolbox_childs[CAT_LAYERS].index) + , "Layers" + , &toolbar_button_background + , &toolbar_button_background_hovered + , &toolbar_button_background_active + , &toolbar_button_size)) + { + m_child_current = static_cast(_toolbox_childs[CAT_LAYERS].index); + m_update_scroll = true; + } + + maxs = ImVec2(mins.x + actual_button_size.x, mins.y + actual_button_size.y); + ImGui::GetWindowDrawList()->AddRect(mins, maxs, ImGui::ColorConvertFloat4ToU32(button_border_color)); + } + + + // ------------------------------------------------------ + + // # + + if (dvars::gui_toolbox_integrate_cam_toolbar && dvars::gui_toolbox_integrate_cam_toolbar->current.enabled) { auto cam_toolbar_active_bg = ImVec4(0.25f, 0.25f, 0.3f, 1.0f); @@ -1929,7 +1976,8 @@ namespace ggui // switch to other child when user no longer has surface inspector / entity properties incorporated if ( (dvars::gui_props_surfinspector && dvars::gui_props_surfinspector->current.integer != 2 && child.first == CAT_SURF_INSP) || (dvars::gui_props_toolbox && !dvars::gui_props_toolbox->current.enabled && child.first == CAT_ENTITY_PROPS) - || (dvars::gui_toolbox_integrate_filter && !dvars::gui_toolbox_integrate_filter->current.enabled && child.first == CAT_FILTER)) + || (dvars::gui_toolbox_integrate_filter && !dvars::gui_toolbox_integrate_filter->current.enabled && child.first == CAT_FILTER) + || (dvars::gui_toolbox_integrate_layers && !dvars::gui_toolbox_integrate_layers->current.enabled && child.first == CAT_LAYERS)) { focus_child(toolbox_dialog::TB_CHILD::BRUSH); break; @@ -1970,6 +2018,7 @@ namespace ggui register_child(CAT_SURF_INSP, std::bind(&toolbox_dialog::child_surface_inspector, this)); register_child(CAT_ENTITY_PROPS, std::bind(&toolbox_dialog::child_entity_properties, this)); register_child(CAT_FILTER, std::bind(&toolbox_dialog::child_filter, this)); + register_child(CAT_LAYERS, std::bind(&toolbox_dialog::child_layers, this)); if (dvars::gui_saved_state_toolbox_child) { @@ -2006,6 +2055,12 @@ namespace ggui /* default */ true, /* flags */ game::dvar_flags::saved, /* desc */ "integrate filter window into toolbox"); + + dvars::gui_toolbox_integrate_layers = dvars::register_bool( + /* name */ "gui_toolbox_integrate_layers", + /* default */ true, + /* flags */ game::dvar_flags::saved, + /* desc */ "integrate layer window into toolbox"); } diff --git a/src/ggui/toolbox.hpp b/src/ggui/toolbox.hpp index 978f119..77d915c 100644 --- a/src/ggui/toolbox.hpp +++ b/src/ggui/toolbox.hpp @@ -22,6 +22,7 @@ namespace ggui const std::string CAT_SURF_INSP = "surface_inspector"; const std::string CAT_ENTITY_PROPS = "entity_properties"; const std::string CAT_FILTER = "Filter"; + const std::string CAT_LAYERS = "Layers"; public: enum class TB_CHILD : int @@ -30,7 +31,8 @@ namespace ggui PATCH, SURFACE_INSP, ENTITY_PROPS, - FILTER + FILTER, + LAYERS }; toolbox_dialog() @@ -68,5 +70,6 @@ namespace ggui void child_surface_inspector(); void child_entity_properties(); void child_filter(); + void child_layers(); }; } From a9c0d835019e4b1104f078f4c23dbe72d54b55bc Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Sat, 20 May 2023 10:18:16 +0200 Subject: [PATCH 07/11] fix surface insp. issues #25 - proper shifting and toggle for orig logic - texture scalar used for width/size no longer affects texture shifting - UI: display correct shifting values (as orig. surface inspector) and inc/dec of 1 will no longer shift by a full texture width/height (made it seem like shifting was not working) - UI: add 'Use Original Logic' checkbox to toggle between new additive/subtractive - per surface - logic and the original surface inspector logic that uses settings of the first face and applies these onto all other selected surfaces/brushes. --- src/ggui/surface_inspector.cpp | 362 ++++++++++++++++++++++----------- src/ggui/surface_inspector.hpp | 3 +- 2 files changed, 241 insertions(+), 124 deletions(-) diff --git a/src/ggui/surface_inspector.cpp b/src/ggui/surface_inspector.cpp index 216a440..55b808f 100644 --- a/src/ggui/surface_inspector.cpp +++ b/src/ggui/surface_inspector.cpp @@ -17,162 +17,251 @@ namespace ggui { - void surface_dialog::edit_texture_info(game::texdef_sub_t* texdef, bool set_specific, TEXMODE mode, int dir) + void surface_dialog::edit_texture_info(game::texdef_sub_t* texdef, texedit_helper* helper, TEXMODE mode, int dir) { const float PATCH_SCALAR = 0.005f; //auto& g_patch_texdef = *reinterpret_cast(0x23F15F8); - FOR_ALL_SELECTED_BRUSHES(sb) - { - // selection is patch - if (sb->patch) - { - if (set_specific) - { - game::printf_to_console("[!] specific texture edits are not supported for patches. skipping patch ..."); - continue; - } + //bool is_dirty = false; - //texdef->shift[0] *= PATCH_SCALAR; - //texdef->shift[1] *= PATCH_SCALAR; + //bool use_additive_logic = false; - if (dir != 0) + if (!helper->original_logic) + { + FOR_ALL_SELECTED_BRUSHES(sb) + { + // selection is patch + if (sb->patch) { - if ((mode & TEXMODE_SHIFT_HORZ) != 0) + if (helper->specific_mode) { - texdef->shift[0] = -texdef->shift[0] * PATCH_SCALAR; + game::printf_to_console("[!] specific texture edits are not supported for patches. skipping patch ..."); + continue; } - if ((mode & TEXMODE_SHIFT_VERT) != 0) - { - texdef->shift[1] = -texdef->shift[1] * PATCH_SCALAR; - } + //texdef->shift[0] *= PATCH_SCALAR; + //texdef->shift[1] *= PATCH_SCALAR; - if ((mode & TEXMODE_SIZE_HORZ) != 0) + if (dir != 0) { - texdef->size[0] = 1.0f - (texdef->size[0] * PATCH_SCALAR); - } + if ((mode & TEXMODE_SHIFT_HORZ) != 0) + { + texdef->shift[0] = -texdef->shift[0] * PATCH_SCALAR; + } + + if ((mode & TEXMODE_SHIFT_VERT) != 0) + { + texdef->shift[1] = -texdef->shift[1] * PATCH_SCALAR; + } - if ((mode & TEXMODE_SIZE_VERT) != 0) + if ((mode & TEXMODE_SIZE_HORZ) != 0) + { + texdef->size[0] = 1.0f - (texdef->size[0] * PATCH_SCALAR); + } + + if ((mode & TEXMODE_SIZE_VERT) != 0) + { + texdef->size[1] = 1.0f - (texdef->size[1] * PATCH_SCALAR); + } + } + else { - texdef->size[1] = 1.0f - (texdef->size[1] * PATCH_SCALAR); + texdef->size[0] *= PATCH_SCALAR; + texdef->size[1] *= PATCH_SCALAR; + texdef->shift[0] *= PATCH_SCALAR; + texdef->shift[1] *= PATCH_SCALAR; } - } - else - { - texdef->size[0] *= PATCH_SCALAR; - texdef->size[1] *= PATCH_SCALAR; - texdef->shift[0] *= PATCH_SCALAR; - texdef->shift[1] *= PATCH_SCALAR; - } - //texdef->rotate *= PATCH_SCALAR; + //texdef->rotate *= PATCH_SCALAR; - // increases version itself - Patch_SetTextureInfo(texdef); - } - else - { - // entire brush selected (not single faces) - // offset texture for each face, ++version to update visuals - if (sb->def && sb->def->brush_faces) + // increases version itself + Patch_SetTextureInfo(texdef); + } + + // not patch + else { - for (auto f = 0; f < sb->def->facecount; f++) + // entire brush selected (not single faces) + // offset texture for each face, ++version to update visuals + if (sb->def && sb->def->brush_faces) { - const auto mat_def = &sb->def->brush_faces[f].mtldef[game::g_qeglobals->current_edit_layer]; - - if (set_specific) + for (auto f = 0; f < sb->def->facecount; f++) { - if ((mode & TEXMODE_SIZE_HORZ) != 0) - { - mat_def->mat_texDef->size[0] = texdef->size[0]; - } + const auto mat_def = &sb->def->brush_faces[f].mtldef[game::g_qeglobals->current_edit_layer]; - if ((mode & TEXMODE_SIZE_VERT) != 0) + if (helper->specific_mode) { - mat_def->mat_texDef->size[1] = texdef->size[1]; + if ((mode & TEXMODE_SIZE_HORZ) != 0) + { + mat_def->mat_texDef->size[0] = texdef->size[0]; + } + + if ((mode & TEXMODE_SIZE_VERT) != 0) + { + mat_def->mat_texDef->size[1] = texdef->size[1]; + } + + if ((mode & TEXMODE_SHIFT_HORZ) != 0) + { + mat_def->mat_texDef->shift[0] = texdef->shift[0]; + } + + if ((mode & TEXMODE_SHIFT_VERT) != 0) + { + mat_def->mat_texDef->shift[1] = texdef->shift[1]; + } + + if ((mode & TEXMODE_ROTATE) != 0) + { + mat_def->mat_texDef->rotate = texdef->rotate; + } } - - if ((mode & TEXMODE_SHIFT_HORZ) != 0) + else { - mat_def->mat_texDef->shift[0] = texdef->shift[0]; + mat_def->mat_texDef->shift[0] += texdef->shift[0]; + mat_def->mat_texDef->shift[1] += texdef->shift[1]; + mat_def->mat_texDef->size[0] += texdef->size[0]; + mat_def->mat_texDef->size[1] += texdef->size[1]; + mat_def->mat_texDef->rotate += texdef->rotate; } - if ((mode & TEXMODE_SHIFT_VERT) != 0) - { - mat_def->mat_texDef->shift[1] = texdef->shift[1]; - } + ++sb->def->version; + //utils::hook::call(0x48F170)(mat_def, 0); + //is_dirty = true; + } + } + } + } - if ((mode & TEXMODE_ROTATE) != 0) - { - mat_def->mat_texDef->rotate = texdef->rotate; - } + // single faces + + const int selected_faces_count = *reinterpret_cast(0x73C714); + const auto selected_faces = game::g_selected_faces(); + + if (selected_faces_count > 0) + { + for (auto f = 0; f < selected_faces_count; f++) + { + const auto face_index = selected_faces[f].index; + const auto face_brush = selected_faces[f].brush->def; + + const auto mat_def = &face_brush->brush_faces[face_index].mtldef[game::g_qeglobals->current_edit_layer]; + + if (helper->specific_mode) + { + if ((mode & TEXMODE_SIZE_HORZ) != 0) + { + mat_def->mat_texDef->size[0] = texdef->size[0]; } - else + + if ((mode & TEXMODE_SIZE_VERT) != 0) { - mat_def->mat_texDef->shift[0] += texdef->shift[0]; - mat_def->mat_texDef->shift[1] += texdef->shift[1]; - mat_def->mat_texDef->size[0] += texdef->size[0]; - mat_def->mat_texDef->size[1] += texdef->size[1]; - mat_def->mat_texDef->rotate += texdef->rotate; + mat_def->mat_texDef->size[1] = texdef->size[1]; } - ++sb->def->version; + if ((mode & TEXMODE_SHIFT_HORZ) != 0) + { + mat_def->mat_texDef->shift[0] = texdef->shift[0]; + } + + if ((mode & TEXMODE_SHIFT_VERT) != 0) + { + mat_def->mat_texDef->shift[1] = texdef->shift[1]; + } + + if ((mode & TEXMODE_ROTATE) != 0) + { + mat_def->mat_texDef->rotate = texdef->rotate; + } + } + else + { + mat_def->mat_texDef->shift[0] += texdef->shift[0]; + mat_def->mat_texDef->shift[1] += texdef->shift[1]; + mat_def->mat_texDef->size[0] += texdef->size[0]; + mat_def->mat_texDef->size[1] += texdef->size[1]; + mat_def->mat_texDef->rotate += texdef->rotate; } + + //utils::hook::call(0x48F170)(mat_def, 0); + ++face_brush->version; + //is_dirty = true; } } } - const int selected_faces_count = *reinterpret_cast(0x73C714); - const auto selected_faces = game::g_selected_faces(); + - if (selected_faces_count > 0) + + // original surface inspector logic which uses texdef settings of the first face + // and applies these onto all other selected surfaces / brushes + else { - for (auto f = 0; f < selected_faces_count; f++) - { - const auto face_index = selected_faces[f].index; - const auto face_brush = selected_faces[f].brush->def; + game::MaterialDef* mtl_def = nullptr; - const auto mat_def = &face_brush->brush_faces[face_index].mtldef[game::g_qeglobals->current_edit_layer]; + const int selected_faces_count = *reinterpret_cast(0x73C714); + const auto selected_faces = game::g_selected_faces(); - if (set_specific) - { - if ((mode & TEXMODE_SIZE_HORZ) != 0) - { - mat_def->mat_texDef->size[0] = texdef->size[0]; - } + // Part of 'Brush_GetMaterialDef' + if (selected_faces_count > 0) + { + const auto b = selected_faces[0].brush; + const auto i = selected_faces[0].index; - if ((mode & TEXMODE_SIZE_VERT) != 0) - { - mat_def->mat_texDef->size[1] = texdef->size[1]; - } + if (selected_faces[0].face != &selected_faces[0].brush->faces[i]) + { + AssertS("selFace.face == &selFace.brush->faces[selFace.index]"); + } + if (b->version != b->def->version) + { + AssertS("selFace.brush->version == selFace.brush->def->version"); + } + // pt = &selected_face->texdef; + mtl_def = &b->def->brush_faces[i].mtldef[game::g_qeglobals->current_edit_layer]; + } + else + { + // pt = &g_qeglobals.d_texturewin.texdef; + mtl_def = (game::MaterialDef*)&game::g_qeglobals->random_texture_stuff[2100 * game::g_qeglobals->current_edit_layer]; + } - if ((mode & TEXMODE_SHIFT_HORZ) != 0) - { - mat_def->mat_texDef->shift[0] = texdef->shift[0]; - } + if (helper->specific_mode) + { + if ((mode & TEXMODE_SIZE_HORZ) != 0) + { + mtl_def->mat_texDef->size[0] = texdef->size[0]; + } - if ((mode & TEXMODE_SHIFT_VERT) != 0) - { - mat_def->mat_texDef->shift[1] = texdef->shift[1]; - } + if ((mode & TEXMODE_SIZE_VERT) != 0) + { + mtl_def->mat_texDef->size[1] = texdef->size[1]; + } - if ((mode & TEXMODE_ROTATE) != 0) - { - mat_def->mat_texDef->rotate = texdef->rotate; - } + if ((mode & TEXMODE_SHIFT_HORZ) != 0) + { + mtl_def->mat_texDef->shift[0] = texdef->shift[0]; } - else + + if ((mode & TEXMODE_SHIFT_VERT) != 0) { - mat_def->mat_texDef->shift[0] += texdef->shift[0]; - mat_def->mat_texDef->shift[1] += texdef->shift[1]; - mat_def->mat_texDef->size[0] += texdef->size[0]; - mat_def->mat_texDef->size[1] += texdef->size[1]; - mat_def->mat_texDef->rotate += texdef->rotate; + mtl_def->mat_texDef->shift[1] = texdef->shift[1]; } - ++face_brush->version; + if ((mode & TEXMODE_ROTATE) != 0) + { + mtl_def->mat_texDef->rotate = texdef->rotate; + } } + else + { + mtl_def->mat_texDef->shift[0] += texdef->shift[0]; + mtl_def->mat_texDef->shift[1] += texdef->shift[1]; + mtl_def->mat_texDef->size[0] += texdef->size[0]; + mtl_def->mat_texDef->size[1] += texdef->size[1]; + mtl_def->mat_texDef->rotate += texdef->rotate; + } + + utils::hook::call(0x48F170)(mtl_def, 0); } } @@ -262,7 +351,7 @@ namespace ggui { const auto mat_def = &selected_faces->brush->def->brush_faces[selected_faces->index].mtldef[game::g_qeglobals->current_edit_layer]; - if(mat_def->radMtl) + if (mat_def->radMtl) { last_texture_name = mat_def->radMtl->name; last_texture_width = mat_def->radMtl->width; @@ -274,8 +363,8 @@ namespace ggui texhelp.size_horz = mat_def->mat_texDef->size[0] * tex_scalar_width; texhelp.size_vert = mat_def->mat_texDef->size[1] * tex_scalar_height; - texhelp.shift_horz = mat_def->mat_texDef->shift[0] * tex_scalar_width; - texhelp.shift_vert = mat_def->mat_texDef->shift[1] * tex_scalar_height; + texhelp.shift_horz = mat_def->mat_texDef->shift[0] /** tex_scalar_width*/; + texhelp.shift_vert = mat_def->mat_texDef->shift[1] /** tex_scalar_height*/; texhelp.rotation = mat_def->mat_texDef->rotate; } } @@ -311,8 +400,8 @@ namespace ggui texhelp.size_horz = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->size[0] * tex_scalar_width; texhelp.size_vert = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->size[1] * tex_scalar_height; - texhelp.shift_horz = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->shift[0] * tex_scalar_width; - texhelp.shift_vert = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->shift[1] * tex_scalar_height; + texhelp.shift_horz = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->shift[0] /** tex_scalar_width*/; + texhelp.shift_vert = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->shift[1] /** tex_scalar_height*/; texhelp.rotation = sb->def->brush_faces->mtldef[game::g_qeglobals->current_edit_layer].mat_texDef->rotate; } } @@ -338,9 +427,9 @@ namespace ggui : texhelp.scalar_direction == 1 ? amount_inc : 0.0f; game::texdef_sub_t texdef_edit = {}; - texdef_edit.shift[0] = edit_amount / tex_scalar_width; + texdef_edit.shift[0] = edit_amount /*/ tex_scalar_width*/; - edit_texture_info(&texdef_edit, texhelp.specific_mode, TEXMODE_SHIFT_HORZ, texhelp.scalar_direction); + edit_texture_info(&texdef_edit, &texhelp, TEXMODE_SHIFT_HORZ, texhelp.scalar_direction); } if (is_toolbox) @@ -358,9 +447,9 @@ namespace ggui : texhelp.scalar_direction == 1 ? amount_inc : 0.0f; game::texdef_sub_t texdef_edit = {}; - texdef_edit.shift[1] = edit_amount / tex_scalar_height; + texdef_edit.shift[1] = edit_amount /*/ tex_scalar_height*/; - edit_texture_info(&texdef_edit, texhelp.specific_mode, TEXMODE_SHIFT_VERT, texhelp.scalar_direction); + edit_texture_info(&texdef_edit, &texhelp, TEXMODE_SHIFT_VERT, texhelp.scalar_direction); } SPACING(0.0f, 4.0f); @@ -382,7 +471,7 @@ namespace ggui game::texdef_sub_t texdef_edit = {}; texdef_edit.size[0] = edit_amount / tex_scalar_width; - edit_texture_info(&texdef_edit, texhelp.specific_mode, TEXMODE_SIZE_HORZ, texhelp.scalar_direction); + edit_texture_info(&texdef_edit, &texhelp, TEXMODE_SIZE_HORZ, texhelp.scalar_direction); } if (is_toolbox) @@ -402,7 +491,7 @@ namespace ggui game::texdef_sub_t texdef_edit = {}; texdef_edit.size[1] = edit_amount / tex_scalar_height; - edit_texture_info(&texdef_edit, texhelp.specific_mode, TEXMODE_SIZE_VERT, texhelp.scalar_direction); + edit_texture_info(&texdef_edit, &texhelp, TEXMODE_SIZE_VERT, texhelp.scalar_direction); } SPACING(0.0f, 4.0f); @@ -424,7 +513,7 @@ namespace ggui game::texdef_sub_t texdef_edit = {}; texdef_edit.rotate = edit_amount; - edit_texture_info(&texdef_edit, texhelp.specific_mode, TEXMODE_ROTATE); + edit_texture_info(&texdef_edit, &texhelp, TEXMODE_ROTATE); } if (is_toolbox) @@ -471,9 +560,34 @@ namespace ggui "- Set all selected faces/brushes to the specified value.\n\n" "Off: (Inc/Dec Mode)\n" "- Texture edits are only possible via the + and - buttons\n" - "- Inc/Dec values per face/brush (eg: brush1 value @ 0.0f + offset, brush2 value @ 45.0f + offset\n\n" + "- Inc/Dec values per face/brush\n (eg: brush1 value @ 0.0f + offset, brush2 value @ 45.0f + offset)\n\n" "Displayed values are always from the first face that was selected."); + // ------ + + ImGui::BeginGroup(); + { + ImGui::Checkbox(" Use Original Logic", &texhelp.original_logic); + ImGui::EndGroup(); + } + + // original surface inspector logic which uses texdef settings of the first face + // and applies these onto all other selected surfaces / brushes + + group_width = ImGui::GetItemRectSize().x; + ImGui::SameLine(); + ImGui::HelpMarker( + "On: (Original Logic)\n" + "- Uses settings of the first face and applies\n" + " these onto all other selected surfaces / brushes.\n" + "- Supports UNDO/REDO.\n\n" + "Off: (Additive - per Face)\n" + "- Inc/Dec values per face/brush\n" + " (eg: brush1 value @ 0.0f + offset, brush2 value @ 45.0f + offset)\n\n" + "Displayed values are always from the first face that was selected."); + + // ------ + if (!is_toolbox) { SPACING(0.0f, 4.0f); @@ -487,12 +601,12 @@ namespace ggui // # // end and begin next node - if(treenode_state) + if (treenode_state) { toolbox_dialog::treenode_end(style_colors, style_vars); } - if(is_toolbox) + if (is_toolbox) { treenode_state = toolbox_dialog::treenode_begin("Texture Operations", true, style_colors, style_vars); } @@ -584,8 +698,10 @@ namespace ggui const char* set_mode_items[] = { "2D", "2D - Auto Set", "3D", "3D - Auto Set", "Patch Curve", "Patch Curve - Auto Set" }; static int set_mode_current = 1; - + + imgui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.125f)); ImGui::Combo(" Mode", &set_mode_current, set_mode_items, IM_ARRAYSIZE(set_mode_items)); + imgui::PopStyleColor(); if (is_toolbox) { diff --git a/src/ggui/surface_inspector.hpp b/src/ggui/surface_inspector.hpp index 845deb6..bc4ffec 100644 --- a/src/ggui/surface_inspector.hpp +++ b/src/ggui/surface_inspector.hpp @@ -80,6 +80,7 @@ namespace ggui int scalar_direction = 0; bool specific_mode = false; + bool original_logic = false; texedit_helper() = default; @@ -128,6 +129,6 @@ namespace ggui TEXMODE_ROTATE = 1 << 4 }; - void edit_texture_info(game::texdef_sub_t* texdef, bool set_specific = false, TEXMODE mode = TEXMODE_NONE, int dir = 0); + void edit_texture_info(game::texdef_sub_t* texdef, texedit_helper* helper, TEXMODE mode = TEXMODE_NONE, int dir = 0); }; } From 68a6878eb25d078a7b2bad637140dac4d3c8e344 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Sun, 28 May 2023 11:29:56 +0200 Subject: [PATCH 08/11] entity info window done; fix a depth issue (outlines on selected object) - entity info: hovering over an entity in the list will highlight it using a bounding box - struct fixes - fix 'flickering' outlines issue on selected objects that was occurring under certain conditions --- src/components/modules/physx_impl.cpp | 3 +- src/components/modules/renderer.cpp | 234 ++++++++++++++++++-------- src/components/modules/renderer.hpp | 7 +- src/game/functions.cpp | 24 +++ src/game/functions.hpp | 8 + src/game/structs.hpp | 33 +++- src/ggui/entity.cpp | 7 +- src/ggui/entity_info.cpp | 154 +++++++++++++++++ src/ggui/entity_info.hpp | 38 +++++ src/ggui/menubar.cpp | 3 +- src/ggui/preferences.cpp | 9 +- src/std_include.hpp | 1 + 12 files changed, 440 insertions(+), 81 deletions(-) create mode 100644 src/ggui/entity_info.cpp create mode 100644 src/ggui/entity_info.hpp diff --git a/src/components/modules/physx_impl.cpp b/src/components/modules/physx_impl.cpp index 34efcc7..4a234b4 100644 --- a/src/components/modules/physx_impl.cpp +++ b/src/components/modules/physx_impl.cpp @@ -1414,8 +1414,7 @@ namespace components p->setAngularVelocity(null_vec); // check - - if (user_data->entity->firstActive->firstBrush->owner && user_data->entity->firstActive->firstBrush->owner->eclass) + if (user_data->entity->firstActive->brushes.oprev->owner && user_data->entity->firstActive->brushes.oprev->owner->eclass) { // reset prefab entity diff --git a/src/components/modules/renderer.cpp b/src/components/modules/renderer.cpp index 8ad78fd..fe2e1e9 100644 --- a/src/components/modules/renderer.cpp +++ b/src/components/modules/renderer.cpp @@ -136,7 +136,7 @@ namespace components bool g_log_rendercommands = false; game::Material* g_invalid_material = nullptr; - bool g_line_depth_testing = true; + //bool g_line_depth_testing = true; // * ---------------------------------------------------------- @@ -212,7 +212,6 @@ namespace components } } - // * // do not cull entities with the custom no-cull flag (used phys prefabs) bool cubic_culling_overwrite_check(game::selbrush_def_t* sb) @@ -1906,7 +1905,7 @@ namespace components { // clear framebuffer (color) renderer::R_SetAndClearSceneTarget(true); - g_line_depth_testing = true; + renderer::get()->set_line_depthtesting(true); } } @@ -3191,18 +3190,6 @@ namespace components } #endif - // spot where a depthbuffer clear command would be added - void __declspec(naked) disable_line_depth_testing() - { - // disable depth testing for outlines - const static uint32_t retn_addr = 0x4084D7; - __asm - { - mov g_line_depth_testing, 0; - jmp retn_addr; - } - } - void draw_additional_debug() { if (game::glob::debug_sundir) @@ -3250,70 +3237,136 @@ namespace components } mesh_painter::on_frame(); + GET_GUI(ggui::entity_info)->render_hovered(); } // * - struct GfxCmdDrawPoints + const int debug_box_edge_pairs[12][2] = { - game::GfxCmdHeader header; - __int16 pointCount; - char width; - char dimensions; - game::GfxPointVertex point; // + 0x8 + {0, 1}, {0, 2}, {0, 4}, {1, 3}, + {1, 5}, {2, 3}, {2, 6}, {3, 7}, + {4, 5}, {4, 6}, {5, 7}, {6, 7}, }; + const int debug_box_edges[23] = + { + 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7, + }; - void renderer::R_AddPointCmd(const std::uint16_t count, const char width, const char dimension, const game::GfxPointVertex* points) + void renderer::add_debug_box(const float* origin, const float* mins, const float* maxs, float yaw, float size_offset, bool depth_test_override, bool depth_test_value) { - if (count <= 0) - { - Assert(); - } + float v[8][3]; - const game::GfxCmdArray* s_cmdList = reinterpret_cast(*(DWORD*)0x73D4A0); - const auto merged_cmd = reinterpret_cast(s_cmdList->lastCmd); + const float fCos = cosf(yaw * 0.017453292f); + const float fSin = sinf(yaw * 0.017453292f); - if (merged_cmd && merged_cmd->header.id == game::RC_DRAW_POINTS - && count * sizeof(game::GfxPointVertex) + (unsigned int)merged_cmd->header.byteCount <= 0xFFFF - && merged_cmd->width == width - && merged_cmd->dimensions == dimension - && count + merged_cmd->pointCount <= 0x7FFF) + for (auto i = 0u; i < 8; ++i) { - // unsure about the name, lets call it R_AddMultipleRendercommands - void* cmds = utils::hook::call(0x4FB0D0)(count * sizeof(game::GfxPointVertex)); - - if (cmds) + for (auto j = 0u; j < 3; ++j) { - memcpy(cmds, points, count * sizeof(game::GfxPointVertex)); - merged_cmd->pointCount += count; + float val; + if ((i & (1 << j)) != 0) + { + val = maxs[j] + size_offset; + } + else + { + val = mins[j] - size_offset; + } + v[i][j] = val; } + + v[i][0] = (v[i][0] * fCos) - (v[i][1] * fSin) + origin[0]; + v[i][1] = (v[i][0] * fSin) + (v[i][1] * fCos) + origin[1]; + v[i][2] += origin[2]; } - else + + const bool old_depth_value = components::renderer::get()->get_line_depthtesting(); + if (depth_test_override) { - const size_t vert_mem_size = count * sizeof(game::GfxPointVertex); - const auto line = reinterpret_cast(game::R_GetCommandBuffer(vert_mem_size + offsetof(GfxCmdDrawPoints, point), game::RC_DRAW_POINTS)); + components::renderer::get()->set_line_depthtesting(depth_test_value); + } - if (line) - { - line->pointCount = count; - line->width = width; - line->dimensions = dimension; - memcpy(&line->point, points, vert_mem_size); - } + for (auto ia = 0u; ia < 12; ++ia) + { + game::GfxPointVertex vert[2]; + vert[0].xyz[0] = v[debug_box_edge_pairs[ia][0]][0]; + vert[0].xyz[1] = v[debug_box_edge_pairs[ia][0]][1]; + vert[0].xyz[2] = v[debug_box_edge_pairs[ia][0]][2]; + vert[0].color.packed = (unsigned)physx::PxDebugColor::eARGB_RED; + + vert[1].xyz[0] = v[debug_box_edges[2 * ia]][0]; + vert[1].xyz[1] = v[debug_box_edges[2 * ia]][1]; + vert[1].xyz[2] = v[debug_box_edges[2 * ia]][2]; + vert[1].color.packed = (unsigned)physx::PxDebugColor::eARGB_RED; + + components::renderer::R_AddLineCmd(1, 2, 3, vert); + } + + if (depth_test_override) + { + components::renderer::get()->set_line_depthtesting(old_depth_value); } } // * - void __declspec(naked) disable_line_depth_testing2() + void set_line_depth_testing_helper_hack(bool val) + { + renderer::get()->set_line_depthtesting(val); + + // # HACK + // constantly add a RC_DRAW_TRIANGLES cmd to fix depth-testing on selection outlines + // (fails when static meshes + a light entity or trigger radius .. is in view) + + const float verts[4][4] = + { + { 0.0f, 0.0f, 10000.0f, 1.0f }, + { 0.0f, 1.0f, 10000.0f, 1.0f }, + { 1.0f, 1.0f, 10000.0f, 1.0f }, + { 1.0f, 0.0f, 10000.0f, 1.0f }, + }; + + game::R_DrawSelectionbox(verts[0]); + } + + + + // spot where a depthbuffer clear command would be added + void __declspec(naked) set_line_depth_testing() + { + // disable depth testing for outlines + const static uint32_t retn_addr = 0x4084D7; + __asm + { + pushad; + push 0; + call set_line_depth_testing_helper_hack; + add esp, 4; + popad; + + jmp retn_addr; + } + } + + void set_line_depth_testing_helper(bool val) + { + renderer::get()->set_line_depthtesting(val); + } + + void __declspec(naked) set_line_depth_testing_2() { // enable depth testing for connection lines const static uint32_t draw_target_connection_lines_func = 0x46A2C0; const static uint32_t retn_addr = 0x40CC26; __asm { - mov g_line_depth_testing, 1; + pushad; + push 1; + call set_line_depth_testing_helper; + add esp, 4; + popad; pushad; call draw_target_connection_lines_func; @@ -3336,7 +3389,7 @@ namespace components game::GfxPointVertex verts[2]; // 0xC (12) }; - // rewrite to add depth_test functionality + // rewrite to add depth_test functionality (set 'g_line_depth_testing') void renderer::R_AddLineCmd(const std::uint16_t count, const char width, const char dimension, const game::GfxPointVertex* verts) { if (count <= 0) @@ -3372,12 +3425,66 @@ namespace components line->lineCount = count; line->width = width; line->dimensions = dimension; - line->depth_test = g_line_depth_testing; + line->depth_test = renderer::get()->g_line_depth_testing; + memcpy(line->verts, verts, vert_mem_size); } } } + // * + + struct GfxCmdDrawPoints + { + game::GfxCmdHeader header; + __int16 pointCount; + char width; + char dimensions; + game::GfxPointVertex point; // + 0x8 + }; + + void renderer::R_AddPointCmd(const std::uint16_t count, const char width, const char dimension, const game::GfxPointVertex* points) + { + if (count <= 0) + { + Assert(); + } + + const game::GfxCmdArray* s_cmdList = reinterpret_cast(*(DWORD*)0x73D4A0); + const auto merged_cmd = reinterpret_cast(s_cmdList->lastCmd); + + if (merged_cmd && merged_cmd->header.id == game::RC_DRAW_POINTS + && count * sizeof(game::GfxPointVertex) + (unsigned int)merged_cmd->header.byteCount <= 0xFFFF + && merged_cmd->width == width + && merged_cmd->dimensions == dimension + && count + merged_cmd->pointCount <= 0x7FFF) + { + // unsure about the name, lets call it R_AddMultipleRendercommands + void* cmds = utils::hook::call(0x4FB0D0)(count * sizeof(game::GfxPointVertex)); + + if (cmds) + { + memcpy(cmds, points, count * sizeof(game::GfxPointVertex)); + merged_cmd->pointCount += count; + } + } + else + { + const size_t vert_mem_size = count * sizeof(game::GfxPointVertex); + const auto line = reinterpret_cast(game::R_GetCommandBuffer(vert_mem_size + offsetof(GfxCmdDrawPoints, point), game::RC_DRAW_POINTS)); + + if (line) + { + line->pointCount = count; + line->width = width; + line->dimensions = dimension; + memcpy(&line->point, points, vert_mem_size); + } + } + } + + // * + // use cmd's depth_test var instead of the hardcoded 1 void __declspec(naked) rb_drawlinescmd_stub() { @@ -3882,14 +3989,12 @@ namespace components utils::hook::set(0x5005F0 + 6, GFX_TARGETWINDOW_COUNT); utils::hook::set(0x5005F9 + 1, GFX_TARGETWINDOW_COUNT); - // stub to register new gfxwindows utils::hook(0x4166D1, begin_registration_internal_stub, HOOK_JUMP).install()->quick(); - // ------------------------------------------------------------------------------------------------ - // rewrite, working fine but no need (only debug) + // rewrite, working fine but not needed (debug) // utils::hook::detour(0x4FE750, RB_DrawEditorSkinnedCached, HK_JUMP); // ------------------------------------------------------------------------------------------------ @@ -3962,15 +4067,12 @@ namespace components // !! spot used to implement effect controlling logic (effects::camera_onpaint_stub) //utils::hook::nop(0x40304D, 5); - // do not add a clearscreen command before adding lines to the scene (would leave no depth info for effects) - utils::hook(0x4084D2, disable_line_depth_testing, HOOK_JUMP).install()->quick(); // disable depth testing for lines (same result as clearing the depthbuffer) - - // ^ nop call that adds connection lines (target->targetname) (after disabled depth testing) and call it before disabling depth testing - //utils::hook::nop(0x408645, 5); + // disable depth testing for outlines on selected brushes + // ^ spot would normally clear the depthbuffer to achieve that but we need depth info for effects later down the line + utils::hook(0x4084D2, set_line_depth_testing, HOOK_JUMP).install()->quick(); - // disable depth testing for lines (same result as clearing the depthbuffer) - // + stub that draws the sundirection debug line - utils::hook(0x40CC21, disable_line_depth_testing2, HOOK_JUMP).install()->quick(); + // enable depth testing for lines + stub that draws additional debug lines (sun direction, mesh painter ...) + utils::hook(0x40CC21, set_line_depth_testing_2, HOOK_JUMP).install()->quick(); // * ------ @@ -4023,7 +4125,7 @@ namespace components // silence assert 'drawSurf.fields.primaryLightIndex doesn't index info->viewInfo->shadowableLightCount' utils::hook::nop(0x55A3A3, 5); - // silence assert '((region == DRAW_SURF_FX_CAMERA_EMISSIVE) || (drawSurf == scene.drawSurfs[region]) || (drawSurf->fields.primarySortKey >= (drawSurf - 1)->fields.primarySortKey))' + // silence assert '((region == DRAW_SURF_FX_CAMERA_EMISSIVE) || (drawSurf == scene.drawSurfs[region]) || (drawSurf->fields.primarySortKey >= (drawSurf - 1)->fields.primarySortKey))' utils::hook::nop(0x52EE95, 5); // * ------ @@ -4068,7 +4170,7 @@ namespace components // write a logfile showing all rendercommands for a single frame - command::register_command("g_log_rendercommands"s, [this](auto) + command::register_command("log_rendercommands"s, [this](auto) { g_log_rendercommands = true; }); diff --git a/src/components/modules/renderer.hpp b/src/components/modules/renderer.hpp index ea9c39a..5dd24f7 100644 --- a/src/components/modules/renderer.hpp +++ b/src/components/modules/renderer.hpp @@ -29,11 +29,16 @@ namespace components static void R_InitDrawSurfListInfo(game::GfxDrawSurfListInfo* list); - static void R_AddPointCmd(const std::uint16_t count, const char width, const char dimension, const game::GfxPointVertex* verts); + static void add_debug_box(const float* origin, const float* mins, const float* maxs, float yaw, float size_offset, bool depth_test_override = false, bool depth_test_value = false); static void R_AddLineCmd(const std::uint16_t count, const char width, const char dimension, const game::GfxPointVertex* verts); + static void R_AddPointCmd(const std::uint16_t count, const char width, const char dimension, const game::GfxPointVertex* verts); static inline int effect_drawsurf_count_ = 0; + void set_line_depthtesting(const bool state) { g_line_depth_testing = state; } + bool get_line_depthtesting() { return g_line_depth_testing; } + bool g_line_depth_testing = true; + // ------ static uint8_t constexpr GFX_TARGETWINDOW_COUNT = 10; diff --git a/src/game/functions.cpp b/src/game/functions.cpp index b3816b9..e8ff243 100644 --- a/src/game/functions.cpp +++ b/src/game/functions.cpp @@ -158,6 +158,18 @@ namespace game return brush; } + game::entity_s* get_entity_insts() + { + const auto entity = reinterpret_cast(*(DWORD*)0x23F1748); + return entity; + } + + game::entity_inst_s* get_entity_insts_next() + { + const auto entity = reinterpret_cast(*(DWORD*)0x23F174C); + return entity; + } + const int& g_selected_faces_count = *reinterpret_cast(0x73C714); // g_selected_faces is a CArray @@ -1223,6 +1235,18 @@ namespace game // * // * --------------------- renderer ------------------------------ + void R_DrawSelectionbox(const float* verts) + { + const static uint32_t func_addr = 0x40CC50; + __asm + { + pushad; + mov esi, verts; + call func_addr; + popad; + } + } + game::GfxCmdHeader* R_GetCommandBuffer(std::uint32_t bytes /*ebx*/, int render_cmd /*edi*/) { const static uint32_t R_RenderBufferCmdCheck_func = 0x4FAEB0; diff --git a/src/game/functions.hpp b/src/game/functions.hpp index c34594d..0bf539a 100644 --- a/src/game/functions.hpp +++ b/src/game/functions.hpp @@ -23,6 +23,7 @@ #define FOR_ALL_SELECTED_BRUSHES(B) for (auto (B) = game::g_selected_brushes_next(); (DWORD*)(B) != game::currSelectedBrushes; (B) = (B)->next) #define FOR_ALL_ACTIVE_BRUSHES(B) for (auto (B) = game::g_active_brushes_next(); (DWORD*)(B) != game::active_brushes_ptr; (B) = (B)->next) +#define FOR_ALL_ENTITY_INSTS(E) for (auto (E) = game::get_entity_insts_next(); (DWORD*)(E) != game::entity_insts_ptr; (E) = (E)->next) // custom brush iter (eg. prefab brushes) - (is_single_brush :: a prefab with a single brush will reference itself) //#define FOR_ALL_BRUSHES(B, B_CURR, B_NEXT) \ @@ -99,6 +100,7 @@ namespace game static DWORD* currSelectedBrushes = (DWORD*)(0x23F1864); // (selected_brushes array pointer) static DWORD* worldEntity_ptr = (DWORD*)(0x25D5B30); // holds pointer to worldEntity static DWORD* g_pParentWnd_ptr = (DWORD*)(0x25D5A70); + static DWORD* entity_insts_ptr = (DWORD*)(0x23F1748); // (entity instances array pointer) extern game::vec3_t vec3_origin; extern game::vec4_t color_white; @@ -169,6 +171,9 @@ namespace game extern game::selbrush_def_t* g_selected_brushes(); extern game::selbrush_def_t* g_selected_brushes_next(); + extern game::entity_s* get_entity_insts(); + extern game::entity_inst_s* get_entity_insts_next(); + extern const int& g_selected_faces_count; extern game::selface_t* g_selected_faces(); extern game::entity_s_def* g_edit_entity(); @@ -360,6 +365,9 @@ namespace game inline auto CL_RenderScene = reinterpret_cast(0x506030); inline auto R_GenerateReflectionImages = reinterpret_cast(0x550F00); + // verts - 4 vertices defining a quad + void R_DrawSelectionbox(const float* verts); + // sampler_index = the index used in shader_vars.h inline auto R_SetSampler = reinterpret_cast(0x538D70); inline auto R_AddCmdDrawFullScreenColoredQuad = reinterpret_cast(0x4FC260); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 60370ce..c505e3a 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -573,7 +573,7 @@ namespace game brush_t *prev; brush_t *next; entity_s *owner; - char pad_0x000C[4]; + entity_s* ownerNext; int editAmount; char pad_0x0014[12]; vec3_t mins; @@ -598,14 +598,26 @@ namespace game selbrush_def_t* active_brushlist_next; }; + struct entity_brushes_s + { + brush_t* oprev; + brush_t* onext; + selbrush_def_t* owner; + selbrush_def_t* ownerNext; + selbrush_def_t* ownerPrev; + brush_t* def; + int unk1; + int refCount; + vec3_t mins; + vec3_t maxs; + }; STATIC_ASSERT_SIZE(entity_brushes_s, 0x38); struct entity_s { entity_s *prev; entity_s *next; entity_s* firstActive; - brush_t *firstBrush; // <- brush substruct, no ptr - char pad_0x0010[52]; + entity_brushes_s brushes; void* modelInst; prefab_s* prefab; //int version; @@ -819,14 +831,15 @@ namespace game selbrush_def_t* prev; // prev selected brush @prev->def selbrush_def_t* next; // next always empty i guess entity_s* owner; - brush_t* ownerNext; - int xx0; + selbrush_def_t* ownerNext; + selbrush_def_t* ownerPrev; brush_t_with_custom_def* def; int facecount; game::faceVis_s* faces; patch_t* patch; std::int16_t version; - std::int16_t unk; + bool cullFlag; + bool unk_bool; int xx1; int xx2; int xx3; @@ -835,6 +848,14 @@ namespace game STATIC_ASSERT_OFFSET(selbrush_def_t, def, 0x14); STATIC_ASSERT_OFFSET(selbrush_def_t, version, 0x24); + struct entity_inst_s + { + entity_inst_s* prev; + entity_inst_s* next; + entity_s* ent; + selbrush_def_t def; + }; + struct selbrush_ptr { selbrush_def_t*p; diff --git a/src/ggui/entity.cpp b/src/ggui/entity.cpp index fde1461..d22ca95 100644 --- a/src/ggui/entity.cpp +++ b/src/ggui/entity.cpp @@ -384,7 +384,6 @@ namespace ggui void entity_dialog::on_mapload_intercept() { const auto gui = GET_GUI(ggui::entity_dialog); - gui->m_sel_list_ent = nullptr; gui->m_edit_entity_class = nullptr; gui->m_edit_entity_changed = false; @@ -394,6 +393,12 @@ namespace ggui // UpdateSelection void entity_dialog::on_update_selection_intercept() { + const auto prefs = GET_GUI(entity_info); + prefs->update_entity_list(); + prefs->m_update_on_unselect = true; + + // # + if (const auto g_edit_ent = game::g_edit_entity(); g_edit_ent) { diff --git a/src/ggui/entity_info.cpp b/src/ggui/entity_info.cpp new file mode 100644 index 0000000..69a09be --- /dev/null +++ b/src/ggui/entity_info.cpp @@ -0,0 +1,154 @@ +#include "std_include.hpp" + +namespace ggui +{ + void entity_info::render_hovered() + { + if (hovered_entity) + { + const auto mins = hovered_entity->def->mins; + const auto maxs = hovered_entity->def->maxs; + + components::renderer::get()->add_debug_box(game::vec3_origin, mins, maxs, 0.0f, 20.0f, true, false); + + // highlighting an entity this way creates issues when hovering an entity which is currently hidden (either via hidden layer or manual hide) + // it will stay invisible until manually re-selected + + //game::GfxColor col_packed = {}; + //game::vec4_t col = { 1.0f, 0.0f, 0.0f, 1.0f }; + //game::Byte4PackPixelColor(col, &col_packed); + //char zero[4] = {}; + + //if (hovered_entity->cullFlag) + //{ + // // draw brush + // utils::hook::call(0x47AFC0) + // (hovered_entity, game::vec3_origin, -1, 0, 4, 0, 4, &col_packed, 2, 1, zero); + //} + } + } + + void entity_info::update_entity_list() + { + entity_list.clear(); + entity_list.reserve(128); + + FOR_ALL_ENTITY_INSTS(e) + { + if (e && e->ent && e->ent->eclass) + { + bool class_exists = false; + for (auto& it : entity_list) + { + if (e->ent->eclass->name == it.class_name) + { + it.entities.emplace_back(e); + class_exists = true; + break; + } + } + + if (!class_exists) + { + // new entity class + entity_list.emplace_back(e->ent->eclass->name, e); + } + } + } + } + + bool entity_info::gui() + { + const auto MIN_WINDOW_SIZE = ImVec2(256.0f, 300.0f); + const auto INITIAL_WINDOW_SIZE = ImVec2(256.0f, 400.0f); + + imgui::SetNextWindowSizeConstraints(MIN_WINDOW_SIZE, ImVec2(FLT_MAX, FLT_MAX)); + imgui::SetNextWindowSize(INITIAL_WINDOW_SIZE, ImGuiCond_FirstUseEver); + imgui::SetNextWindowPos(ggui::get_initial_window_pos(), ImGuiCond_FirstUseEver); + + if (!imgui::Begin("Entity Info##window", this->get_p_open(), ImGuiWindowFlags_NoCollapse)) + { + imgui::End(); + return false; + } + + // # + + SPACING(0.0f, 2.0f); + imgui::Indent(8.0f); + + if (m_update_on_unselect && !game::is_any_brush_selected()) + { + m_update_on_unselect = false; + update_entity_list(); + } + + if (!entity_list.empty()) + { + bool any_item_hovered = false; + int node_count = 0; + + for (const auto& it : entity_list) + { + imgui::PushID(node_count); + if (imgui::TreeNode(it.class_name.c_str())) + { + int ent_count = 0; + + for (const auto e : it.entities) // cant be a const& because 'Select_Deselect' can invalidate the pointer + { + imgui::PushID(ent_count); + if (imgui::Selectable(it.class_name.c_str())) + { + // calling this destroys invalidates 'e' when e is a const& (because lambda func?) + game::Select_Deselect(true); + + auto onext = e->def.ownerNext; + + for (const auto i = &e->def; onext != i; onext = onext->ownerNext) + { + game::Brush_Select(onext, true, false, true); + //components::command::execute("center_camera_on_selection"); + } + } + + if (imgui::IsItemHovered(ImGuiHoveredFlags_None)) + { + hovered_entity = e->def.ownerNext; + any_item_hovered = true; + } + imgui::PopID(); + ent_count++; + + } + + imgui::TreePop(); + } + + imgui::PopID(); + node_count++; + } + + if (!any_item_hovered) + { + hovered_entity = nullptr; + } + } + + imgui::End(); + return true; + } + + void entity_info::on_init() + { } + + void entity_info::on_open() + { + update_entity_list(); + } + + void entity_info::on_close() + { } + + REGISTER_GUI(entity_info); +} diff --git a/src/ggui/entity_info.hpp b/src/ggui/entity_info.hpp new file mode 100644 index 0000000..f2896e7 --- /dev/null +++ b/src/ggui/entity_info.hpp @@ -0,0 +1,38 @@ +#pragma once + +namespace ggui +{ + class entity_info final : public ggui::ggui_module + { + public: + entity_info() { set_gui_type(GUI_TYPE_DEF); } + + + // * + // public member functions + + bool gui() override; + void on_init() override; + void on_open() override; + void on_close() override; + + void update_entity_list(); + void render_hovered(); + + struct entity_list_s + { + std::string class_name; + std::vector entities; + + entity_list_s(const std::string& name, game::entity_inst_s* ent) + { + class_name = name; + entities.emplace_back(ent); + } + }; + + bool m_update_on_unselect = false; + game::selbrush_def_t* hovered_entity = nullptr; + std::vector entity_list; + }; +} diff --git a/src/ggui/menubar.cpp b/src/ggui/menubar.cpp index 3745b6f..38b1aab 100644 --- a/src/ggui/menubar.cpp +++ b/src/ggui/menubar.cpp @@ -251,7 +251,8 @@ namespace ggui } if (ImGui::MenuItem("Entity Info")) { - cdeclcall(void, 0x426D40); //cmainframe::OnEditEntityinfo + //cdeclcall(void, 0x426D40); //cmainframe::OnEditEntityinfo + GET_GUI(ggui::entity_info)->toggle(); } SEPERATORV(0.0f); diff --git a/src/ggui/preferences.cpp b/src/ggui/preferences.cpp index 70900e7..643fed6 100644 --- a/src/ggui/preferences.cpp +++ b/src/ggui/preferences.cpp @@ -492,7 +492,7 @@ namespace ggui ctexwnd::load_favourites(); } - if(ImGui::Button("Texture filter test")) + if (ImGui::Button("Texture filter test")) { // hide all game::qtexture_s* tex = texwndglob_textures; @@ -504,7 +504,7 @@ namespace ggui tex = texwndglob_textures; for (; tex; tex = tex->prev) { - for(auto m = 0; m < IM_ARRAYSIZE(mat_names); m++) + for (auto m = 0; m < IM_ARRAYSIZE(mat_names); m++) { if (utils::string_equals(tex->name, mat_names[m])) { @@ -516,7 +516,7 @@ namespace ggui SPACING(0.0f, 4.0f); - if(ImGui::Button("Toast Success")) + if (ImGui::Button("Toast Success")) { ImGui::InsertNotification( { ImGuiToastType_Success, 2000, "Hello World! This is a success! %s", ICON_FA_APPLE_ALT}); @@ -612,9 +612,10 @@ namespace ggui ImGui::ColorEdit4("Color 01", dev_color_01, ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR); ImGui::ColorEdit4("Color 02", dev_color_02, ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR); ImGui::ColorEdit4("Color 03", dev_color_03, ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR); + }); } - + bool preferences_dialog::gui() { const auto MIN_WINDOW_SIZE = ImVec2(800.0f, 400.0f); diff --git a/src/std_include.hpp b/src/std_include.hpp index 717ff55..788622f 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -156,6 +156,7 @@ #include "ggui/effects_browser.hpp" #include "ggui/effects_editor_gui.hpp" #include "ggui/entity.hpp" +#include "ggui/entity_info.hpp" #include "ggui/filter.hpp" #include "ggui/grid.hpp" #include "ggui/help.hpp" From 139f2491d585a0920e19123feba016e44a9dfdb9 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Sun, 28 May 2023 19:21:12 +0200 Subject: [PATCH 09/11] tint selected light entities; grid: fix entity classname coloring - tint selected light entities like every other selection - classnames drawn on the grid used a color that was changing when using the windows color dialog (K). > switched to a different saved color --- src/common/cxywnd.cpp | 2 +- src/components/modules/renderer.cpp | 49 +++++++++++++++++++++++++++-- src/game/functions.hpp | 1 + src/ggui/colors.cpp | 5 ++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/common/cxywnd.cpp b/src/common/cxywnd.cpp index 66fbd1d..207d4e9 100644 --- a/src/common/cxywnd.cpp +++ b/src/common/cxywnd.cpp @@ -133,7 +133,7 @@ void __declspec(naked) draw_grid_block_text_stub() // fix entity name drawing void draw_entity_names_hk(const char* text, game::Font_s* font, float* origin, float* pixel_step_x, float* pixel_step_y, [[maybe_unused]] float* color) { - components::renderer::R_AddCmdDrawTextAtPosition(text, font, origin, pixel_step_x, pixel_step_y, game::g_qeglobals->d_savedinfo.colors[game::COLOR_ENTITYUNK]); + components::renderer::R_AddCmdDrawTextAtPosition(text, font, origin, pixel_step_x, pixel_step_y, game::g_qeglobals->d_savedinfo.colors[game::COLOR_VIEWNAME]); // game::COLOR_ENTITYUNK (changes when using the colordialog tool) } bool g_block_radiant_modeldialog = false; diff --git a/src/components/modules/renderer.cpp b/src/components/modules/renderer.cpp index fe2e1e9..89b6c3e 100644 --- a/src/components/modules/renderer.cpp +++ b/src/components/modules/renderer.cpp @@ -3331,8 +3331,6 @@ namespace components game::R_DrawSelectionbox(verts[0]); } - - // spot where a depthbuffer clear command would be added void __declspec(naked) set_line_depth_testing() { @@ -3377,6 +3375,49 @@ namespace components } } + // * + + void light_selection_tint() + { + game::R_SetMaterialColor(game::g_qeglobals->d_savedinfo.colors[11]); + } + + void __declspec(naked) light_selection_tint_stub() + { + // enable depth testing for connection lines + const static uint32_t func_addr = 0x4FD910; + const static uint32_t retn_addr = 0x408302; + __asm + { + call func_addr; + pushad; + call light_selection_tint; + popad; + jmp retn_addr; + } + } + + void light_selection_tint_reset() + { + game::R_SetMaterialColor(nullptr); + } + + void __declspec(naked) light_selection_tint_reset_stub() + { + const static uint32_t func_addr = 0x4FD910; + const static uint32_t retn_addr = 0x4083BE; + __asm + { + call func_addr; + pushad; + call light_selection_tint_reset; + popad; + jmp retn_addr; + } + } + + // * + // add depth_test struct GfxCmdDrawLines { @@ -4074,6 +4115,10 @@ namespace components // enable depth testing for lines + stub that draws additional debug lines (sun direction, mesh painter ...) utils::hook(0x40CC21, set_line_depth_testing_2, HOOK_JUMP).install()->quick(); + // tint selected light entities + utils::hook(0x4082FD, light_selection_tint_stub, HOOK_JUMP).install()->quick(); + utils::hook(0x4083B9, light_selection_tint_reset_stub, HOOK_JUMP).install()->quick(); + // * ------ // rewrite R_AddLineCmd to add depth_test functionality diff --git a/src/game/functions.hpp b/src/game/functions.hpp index 0bf539a..9660ab6 100644 --- a/src/game/functions.hpp +++ b/src/game/functions.hpp @@ -371,6 +371,7 @@ namespace game // sampler_index = the index used in shader_vars.h inline auto R_SetSampler = reinterpret_cast(0x538D70); inline auto R_AddCmdDrawFullScreenColoredQuad = reinterpret_cast(0x4FC260); + inline auto R_SetMaterialColor = reinterpret_cast(0x4FC2C0); inline auto R_DrawSurfs = reinterpret_cast(0x5324E0); inline auto R_ShowTris = reinterpret_cast(0x55B100); inline auto R_InitCmdBufSourceState = reinterpret_cast(0x53CB20); diff --git a/src/ggui/colors.cpp b/src/ggui/colors.cpp index a6fd870..4664852 100644 --- a/src/ggui/colors.cpp +++ b/src/ggui/colors.cpp @@ -41,7 +41,8 @@ namespace ggui ImGui::ColorEdit4("Grid Major", game::g_qeglobals->d_savedinfo.colors[game::COLOR_GRIDMAJOR], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Grid Block", game::g_qeglobals->d_savedinfo.colors[game::COLOR_GRIDBLOCK], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Grid Text", game::g_qeglobals->d_savedinfo.colors[game::COLOR_GRIDTEXT], ImGuiColorEditFlags_Float); - ImGui::ColorEdit4("Grid Entity Classname", game::g_qeglobals->d_savedinfo.colors[game::COLOR_ENTITYUNK], ImGuiColorEditFlags_Float); + //ImGui::ColorEdit4("Grid Entity Classname", game::g_qeglobals->d_savedinfo.colors[game::COLOR_ENTITYUNK], ImGuiColorEditFlags_Float); // this holds the latest color used in the windows color dialog (K) + ImGui::ColorEdit4("Grid Entity Classname", game::g_qeglobals->d_savedinfo.colors[game::COLOR_VIEWNAME], ImGuiColorEditFlags_Float); SEPERATORV(0.0f); @@ -66,11 +67,9 @@ namespace ggui SEPERATORV(0.0f); - ImGui::ColorEdit4("Viewname", game::g_qeglobals->d_savedinfo.colors[game::COLOR_VIEWNAME], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Func Group", game::g_qeglobals->d_savedinfo.colors[game::COLOR_FUNC_GROUP], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Func Cull Group", game::g_qeglobals->d_savedinfo.colors[game::COLOR_FUNC_CULLGROUP], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Entity", game::g_qeglobals->d_savedinfo.colors[game::COLOR_ENTITY], ImGuiColorEditFlags_Float); - //ImGui::ColorEdit4("Entity Unkown", game::g_qeglobals->d_savedinfo.colors[game::COLOR_ENTITYUNK], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Togglesurfs", game::g_qeglobals->d_savedinfo.colors[game::COLOR_DRAW_TOGGLESUFS], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Unkown##1", game::g_qeglobals->d_savedinfo.colors[game::COLOR_UNKOWN2], ImGuiColorEditFlags_Float); ImGui::ColorEdit4("Unkown##2", game::g_qeglobals->d_savedinfo.colors[game::COLOR_UNKOWN3], ImGuiColorEditFlags_Float); From b4990c609988f5ce360760b5b56a209ddf7e5b30 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Sun, 28 May 2023 21:31:47 +0200 Subject: [PATCH 10/11] menubar separator --- src/ggui/menubar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ggui/menubar.cpp b/src/ggui/menubar.cpp index 38b1aab..b780c08 100644 --- a/src/ggui/menubar.cpp +++ b/src/ggui/menubar.cpp @@ -1942,7 +1942,7 @@ namespace ggui ImGui::EndMenu(); // Patch } - SPACING(4.0f, 0.0f); + imgui::TextUnformatted("|"); if (ImGui::MenuItem("Help")) { From 484630d68f7657d8258521961abab7dabb332cb2 Mon Sep 17 00:00:00 2001 From: xoxor4d <45299104+xoxor4d@users.noreply.github.com> Date: Sun, 28 May 2023 21:55:05 +0200 Subject: [PATCH 11/11] add changelog link --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35c73bc..22f85e5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,10 @@ the dated MFC/Windows UI with a more modern and flexible UI powered by Dear ImGu
### Project Page (Guide / In-Depth) -https://xoxor4d.github.io/projects/iw3xo-radiant +xoxor4d.github.io/projects/iw3xo-radiant + +#### Changelog +github.com/xoxor4d/iw3xo-radiant/wiki/Changelog