From 7a501c054bde2a538b57e4f01f5a07db73500fb9 Mon Sep 17 00:00:00 2001 From: SuperCake Date: Wed, 29 Jul 2026 22:49:15 +0200 Subject: [PATCH 1/6] fix(chall_select): make interactive in briefing --- .../client/swarm/gameui/swarm/rd_challenge_selection.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp index 25506afa1..fbbcfe904 100644 --- a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp +++ b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp @@ -219,6 +219,13 @@ const CReactiveDropWorkshop::WorkshopItem_t &BaseModUI::ReactiveDropChallengeSel BaseModUI::ReactiveDropChallengeSelection::ReactiveDropChallengeSelection( vgui::Panel *parent, const char *panelName, bool bDeathmatch ) : BaseClass( parent, panelName ) { SetProportional( true ); + SetMouseInputEnabled( true ); + SetKeyBoardInputEnabled( true ); + + if ( engine->IsConnected() ) + { + MakePopup( false ); + } m_pHeaderFooter = new CNB_Header_Footer( this, "HeaderFooter" ); m_pHeaderFooter->SetTitle( "" ); From 5ce86a69f8c55a3ef2ca4261ccf26726e98848a6 Mon Sep 17 00:00:00 2001 From: SuperCake Date: Mon, 27 Jul 2026 11:06:56 +0200 Subject: [PATCH 2/6] rf(chall_select): separate validation and list populating --- .../gameui/swarm/rd_challenge_selection.cpp | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp index fbbcfe904..2483ac9a8 100644 --- a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp +++ b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp @@ -326,18 +326,36 @@ void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges() Assert( g_ReactiveDropWorkshop.m_bStartingUp == false ); g_ReactiveDropWorkshop.m_bStartingUp = true; - int iCount = ReactiveDropChallenges::Count(); - for ( int i = 0; i < iCount; i++ ) + const char* szChallenges[RD_MAX_CHALLENGES]; + int nChallenges = 0; + + // Find valid challenges { - const RD_Challenge_t *pChallenge = ReactiveDropChallenges::GetSummary( i ); - if ( m_bDeathmatch ? pChallenge->AllowDeathmatch : pChallenge->AllowCoop ) + const int nRDChallenges = ReactiveDropChallenges::Count(); + Assert( nRDChallenges <= RD_MAX_CHALLENGES ); + for ( int i = 0; i < nRDChallenges; i++ ) { - ReactiveDropChallengeSelectionListItem *pItem = m_gplChallenges->AddPanelItem( "ReactiveDropChallengeSelectionListItem" ); - pItem->PopulateChallenge( ReactiveDropChallenges::Name( i ) ); - GetControllerFocus()->AddToFocusList( pItem, true, true ); + const RD_Challenge_t *pChallenge = ReactiveDropChallenges::GetSummary( i ); + if ( m_bDeathmatch ? pChallenge->AllowDeathmatch : pChallenge->AllowCoop ) + { + const char *pChallengeName = ReactiveDropChallenges::Name( i ); + + { + szChallenges[nChallenges] = pChallengeName; + nChallenges++; + } + } } } + // Add valid challenges to the panel list + for ( int j = 0; j < nChallenges; j++ ) + { + ReactiveDropChallengeSelectionListItem *pItem = m_gplChallenges->AddPanelItem( "ReactiveDropChallengeSelectionListItem" ); + pItem->PopulateChallenge( szChallenges[j] ); + GetControllerFocus()->AddToFocusList( pItem, true, true ); + } + g_ReactiveDropWorkshop.m_bStartingUp = false; g_ReactiveDropWorkshop.RestartEnabledAddonsQuery(); } From a325890b1285a74a5c5d3b1087ca12a069342522 Mon Sep 17 00:00:00 2001 From: SuperCake Date: Sun, 26 Jul 2026 20:11:32 +0200 Subject: [PATCH 3/6] track reactivedropchallengeselection.res + fix tabulation and do unix-style linebreaks --- .../reactivedropchallengeselection.res | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res diff --git a/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res b/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res new file mode 100644 index 000000000..7835b3d4e --- /dev/null +++ b/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res @@ -0,0 +1,184 @@ +"Resource/UI/BaseModUI/ReactiveDropChallengeSelection.res" +{ + "ReactiveDropChallengeSelection" + { + "ControlName" "Frame" + "fieldName" "ReactiveDropChallengeSelection" + + "xpos" "0" + "ypos" "0" + "zpos" "50" + "wide" "f0" + "tall" "f0" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "0" + } + + "Title" + { + "fieldName" "Title" + "xpos" "c-266" + "ypos" "56" + "wide" "150" + "tall" "19" + "zpos" "5" + "font" "DefaultExtraLarge" + "textAlignment" "west" + "ControlName" "Label" + "labelText" "#rd_challenge_selection_title" + "fgcolor_override" "224 224 224 255" + } + + "Divider1" + { + "ControlName" "ImagePanel" + "fieldName" "Divider1" + "xpos" "c-238" + "ypos" "115" + "zpos" "2" + "wide" "450" + "tall" "2" + "autoResize" "0" + "pinCorner" "0" + "visible" "0" + "enabled" "1" + "tabPosition" "0" + "image" "divider_gradient" + "scaleImage" "1" + } + + "GplChallenges" + { + "ControlName" "GenericPanelList" + "fieldName" "GplChallenges" + "xpos" "c-226" + "ypos" "115" + "wide" "450" + "tall" "185" + "zpos" "1" + "autoResize" "1" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "1" + "proportionalToParent" "1" + + "bgcolor_override" "0 0 0 128" + } + + "Divider2" + { + "ControlName" "ImagePanel" + "fieldName" "Divider2" + "xpos" "c-238" + "ypos" "297" + "zpos" "2" + "wide" "450" + "tall" "2" + "autoResize" "0" + "pinCorner" "0" + "visible" "0" + "enabled" "1" + "tabPosition" "0" + "image" "divider_gradient" + "scaleImage" "1" + } + + "LblName" + { + "ControlName" "Label" + "fieldName" "LblName" + "xpos" "c-140" + "ypos" "315" + "wide" "265" + "tall" "18" + "zpos" "1" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "0" + "font" "DefaultLarge" + "textAlignment" "north-west" + } + + "LblAuthor" + { + "ControlName" "Label" + "fieldName" "LblAuthor" + "xpos" "c+45" + "ypos" "337" + "wide" "180" + "tall" "15" + "zpos" "1" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "0" + "Font" "DefaultMedium" + "textAlignment" "north-east" + "wrap" "0" + "dulltext" "1" + } + + "LblDescription" + { + "ControlName" "Label" + "fieldName" "LblDescription" + "xpos" "c-140" + "ypos" "355" + "zpos" "1" + "wide" "365" + "tall" "75" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "0" + "Font" "Default" + "textAlignment" "north-west" + "wrap" "1" + "brighttext" "1" + "dulltext" "0" + } + + "ImgIcon" + { + "ControlName" "ImagePanel" + "fieldName" "ImgIcon" + "xpos" "c-280" + "ypos" "315" + "zpos" "1" + "wide" "123" + "tall" "69" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "0" + "scaleImage" "1" + } + + "BackButton" + { + "ControlName" "CNB_Button" + "fieldName" "BackButton" + "xpos" "c-264" + "ypos" "r23" + "wide" "117" + "tall" "27" + "zpos" "1" + "visible" "1" + "enabled" "1" + "tabPosition" "0" + "labelText" "#nb_back" + "command" "BackButton" + "textAlignment" "center" + "font" "DefaultMedium" + "fgcolor_override" "113 142 181 255" + } +} From 4981bf8ca0055d8013ae4c06bc1714dc9e4865ac Mon Sep 17 00:00:00 2001 From: SuperCake Date: Sun, 26 Jul 2026 20:34:29 +0200 Subject: [PATCH 4/6] feat: define constant RD_CHALLENGE_TITLE_LEN --- src/game/shared/swarm/rd_challenges_shared.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game/shared/swarm/rd_challenges_shared.h b/src/game/shared/swarm/rd_challenges_shared.h index a678928ee..3946f010a 100644 --- a/src/game/shared/swarm/rd_challenges_shared.h +++ b/src/game/shared/swarm/rd_challenges_shared.h @@ -5,6 +5,7 @@ class KeyValues; #define RD_MAX_CHALLENGES 1024 +#define RD_CHALLENGE_TITLE_LEN 255 #pragma pack(push, 1) struct RD_Challenge_t @@ -19,7 +20,7 @@ struct RD_Challenge_t bool RequiredOnClient : 1; bool _Reserved1 : 1; // Title must be last in this struct - char Title[255]; + char Title[RD_CHALLENGE_TITLE_LEN]; }; #pragma pack(pop) From 59bc529c941c801689ee22c9d2abe12e5e3a5002 Mon Sep 17 00:00:00 2001 From: SuperCake Date: Mon, 27 Jul 2026 11:23:09 +0200 Subject: [PATCH 5/6] feat(chall_select): add search bar --- .../reactivedropchallengeselection.res | 12 +++++ .../gameui/swarm/rd_challenge_selection.cpp | 46 ++++++++++++++++++- .../gameui/swarm/rd_challenge_selection.h | 17 ++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res b/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res index 7835b3d4e..813c15e19 100644 --- a/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res +++ b/reactivedrop/resource/ui/basemodui/reactivedropchallengeselection.res @@ -50,6 +50,18 @@ "scaleImage" "1" } + "TxtSearch" + { + "ControlName" "ReactiveDropChallengeSelectionSearch" + "fieldName" "TxtSearch" + "xpos" "c-226" + "ypos" "88" + "wide" "160" + "tall" "15" + "visible" "1" + "enabled" "1" + } + "GplChallenges" { "ControlName" "GenericPanelList" diff --git a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp index 2483ac9a8..6bb1c945c 100644 --- a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp +++ b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp @@ -1,4 +1,6 @@ #include "cbase.h" + +#include "asw_util_shared.h" #include "rd_challenge_selection.h" #include "rd_challenges_shared.h" #include @@ -6,6 +8,7 @@ #include #include "nb_header_footer.h" #include "nb_button.h" +#include "strtools.h" #include "vfooterpanel.h" #include "rd_workshop.h" #include "filesystem.h" @@ -14,6 +17,43 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +bool NameHasSearch( const char *szTitle, const char *szSearch ) +{ + if ( szSearch[0] == '\0' ) + { + return true; + } + + if ( szTitle[0] == '#' ) + { + wchar_t wszLocalized[RD_CHALLENGE_TITLE_LEN]; + char szLocalized[RD_CHALLENGE_TITLE_LEN]; + + TryLocalize( szTitle, wszLocalized, RD_CHALLENGE_TITLE_LEN ); + V_UnicodeToUTF8( wszLocalized, szLocalized, RD_CHALLENGE_TITLE_LEN ); + + return Q_stristr( szLocalized, szSearch ) != NULL; + } + + return Q_stristr( szTitle, szSearch ) != NULL; +} + +BaseModUI::ReactiveDropChallengeSelectionSearch::ReactiveDropChallengeSelectionSearch( vgui::Panel *parent, const char *panelName, ReactiveDropChallengeSelection *pSelection ) : BaseClass( parent, panelName ) +{ + SetMaximumCharCount( RD_CHALLENGE_TITLE_LEN ); + m_pSelection = pSelection; +} + +void BaseModUI::ReactiveDropChallengeSelectionSearch::OnKeyTyped( wchar_t unichar ) +{ + BaseClass::OnKeyTyped( unichar ); + + char szSearchText[RD_CHALLENGE_TITLE_LEN]; + GetText( szSearchText, RD_CHALLENGE_TITLE_LEN ); + + m_pSelection->PopulateChallenges( szSearchText ); +} + BaseModUI::ReactiveDropChallengeSelectionListItem::ReactiveDropChallengeSelectionListItem( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, panelName ) { SetProportional( true ); @@ -244,6 +284,8 @@ BaseModUI::ReactiveDropChallengeSelection::ReactiveDropChallengeSelection( vgui: m_lblDescription = new vgui::Label( this, "LblDescription", "" ); m_lblAuthor = new vgui::Label( this, "LblAuthor", "" ); + m_pTxtSearch = new ReactiveDropChallengeSelectionSearch( this, "TxtSearch", this ); + m_bIgnoreSelectionChange = false; m_bDeathmatch = bDeathmatch; @@ -316,7 +358,7 @@ void BaseModUI::ReactiveDropChallengeSelection::OnMessage( const KeyValues *para } } -void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges() +void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges( const char* szSearch ) { m_gplChallenges->RemoveAllPanelItems(); ReactiveDropChallengeSelectionListItem *pDisabled = m_gplChallenges->AddPanelItem( "ReactiveDropChallengeSelectionListItem" ); @@ -339,7 +381,9 @@ void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges() if ( m_bDeathmatch ? pChallenge->AllowDeathmatch : pChallenge->AllowCoop ) { const char *pChallengeName = ReactiveDropChallenges::Name( i ); + const char *szTitle = pChallenge->Title; + if ( !szSearch || NameHasSearch(szTitle, szSearch) ) { szChallenges[nChallenges] = pChallengeName; nChallenges++; diff --git a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h index 88f7ba6fc..4dd447ace 100644 --- a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h +++ b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h @@ -4,6 +4,7 @@ #include "basemodui.h" #include "vgenericpanellist.h" +#include "vgui_controls/TextEntry.h" #include "rd_workshop.h" class CNB_Header_Footer; @@ -11,6 +12,18 @@ class CNB_Button; namespace BaseModUI { + class ReactiveDropChallengeSelectionSearch : public vgui::TextEntry + { + DECLARE_CLASS_SIMPLE( ReactiveDropChallengeSelectionSearch, vgui::TextEntry ); + public: + ReactiveDropChallengeSelectionSearch( vgui::Panel *parent, const char *panelName, ReactiveDropChallengeSelection *pSelection ); + + virtual void OnKeyTyped( wchar_t unichar ); + + protected: + ReactiveDropChallengeSelection *m_pSelection; + }; + class ReactiveDropChallengeSelectionListItem : public vgui::EditablePanel, IGenericPanelListItem { DECLARE_CLASS_SIMPLE( ReactiveDropChallengeSelectionListItem, vgui::EditablePanel ); @@ -61,7 +74,7 @@ namespace BaseModUI virtual void OnMessage( const KeyValues *params, vgui::VPANEL ifromPanel ); void UpdateFooter(); - void PopulateChallenges(); + void PopulateChallenges( const char* szSearch = NULL ); void SetDetailsForChallenge( ReactiveDropChallengeSelectionListItem *pChallenge ); CNB_Header_Footer *m_pHeaderFooter; @@ -73,6 +86,8 @@ namespace BaseModUI vgui::Label *m_lblDescription; vgui::Label *m_lblAuthor; + ReactiveDropChallengeSelectionSearch *m_pTxtSearch; + bool m_bIgnoreSelectionChange; bool m_bDeathmatch; }; From 38d8d56b07c0f2d2bddf07f29d8f7a7ed9132c83 Mon Sep 17 00:00:00 2001 From: SuperCake Date: Mon, 27 Jul 2026 12:24:24 +0200 Subject: [PATCH 6/6] feat(chall_select): incremental search --- .../gameui/swarm/rd_challenge_selection.cpp | 27 +++++++++++++++++-- .../gameui/swarm/rd_challenge_selection.h | 7 ++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp index 6bb1c945c..10eae0c96 100644 --- a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp +++ b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.cpp @@ -46,12 +46,16 @@ BaseModUI::ReactiveDropChallengeSelectionSearch::ReactiveDropChallengeSelectionS void BaseModUI::ReactiveDropChallengeSelectionSearch::OnKeyTyped( wchar_t unichar ) { + const int nBefore = GetTextLength(); BaseClass::OnKeyTyped( unichar ); + const int nAfter = GetTextLength(); char szSearchText[RD_CHALLENGE_TITLE_LEN]; GetText( szSearchText, RD_CHALLENGE_TITLE_LEN ); - m_pSelection->PopulateChallenges( szSearchText ); + const bool bIncrSearch = nBefore != 0 && nAfter > nBefore; + + m_pSelection->PopulateChallenges( szSearchText, bIncrSearch ); } BaseModUI::ReactiveDropChallengeSelectionListItem::ReactiveDropChallengeSelectionListItem( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, panelName ) @@ -358,7 +362,7 @@ void BaseModUI::ReactiveDropChallengeSelection::OnMessage( const KeyValues *para } } -void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges( const char* szSearch ) +void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges( const char* szSearch, bool bIncrSearch ) { m_gplChallenges->RemoveAllPanelItems(); ReactiveDropChallengeSelectionListItem *pDisabled = m_gplChallenges->AddPanelItem( "ReactiveDropChallengeSelectionListItem" ); @@ -372,6 +376,23 @@ void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges( const char* int nChallenges = 0; // Find valid challenges + if ( bIncrSearch ) + { + for ( int i = 0; i < m_nChallengesPrev; i++ ) + { + const char *pChallengeName = m_szChallengesPrev[i]; + const char *szTitle = ReactiveDropChallenges::DisplayName(pChallengeName); + + if ( NameHasSearch(szTitle, szSearch) ) + { + szChallenges[nChallenges] = pChallengeName; + m_szChallengesPrev[nChallenges] = pChallengeName; + nChallenges++; + } + } + m_nChallengesPrev = nChallenges; + } + else { const int nRDChallenges = ReactiveDropChallenges::Count(); Assert( nRDChallenges <= RD_MAX_CHALLENGES ); @@ -386,10 +407,12 @@ void BaseModUI::ReactiveDropChallengeSelection::PopulateChallenges( const char* if ( !szSearch || NameHasSearch(szTitle, szSearch) ) { szChallenges[nChallenges] = pChallengeName; + m_szChallengesPrev[nChallenges] = pChallengeName; nChallenges++; } } } + m_nChallengesPrev = nChallenges; } // Add valid challenges to the panel list diff --git a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h index 4dd447ace..fd09480d6 100644 --- a/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h +++ b/src/game/client/swarm/gameui/swarm/rd_challenge_selection.h @@ -5,6 +5,7 @@ #include "basemodui.h" #include "vgenericpanellist.h" #include "vgui_controls/TextEntry.h" +#include "rd_challenges_shared.h" #include "rd_workshop.h" class CNB_Header_Footer; @@ -74,7 +75,7 @@ namespace BaseModUI virtual void OnMessage( const KeyValues *params, vgui::VPANEL ifromPanel ); void UpdateFooter(); - void PopulateChallenges( const char* szSearch = NULL ); + void PopulateChallenges( const char* szSearch = NULL, bool bIncrSearch = false ); void SetDetailsForChallenge( ReactiveDropChallengeSelectionListItem *pChallenge ); CNB_Header_Footer *m_pHeaderFooter; @@ -90,6 +91,10 @@ namespace BaseModUI bool m_bIgnoreSelectionChange; bool m_bDeathmatch; + + private: + const char* m_szChallengesPrev[RD_MAX_CHALLENGES]; + int m_nChallengesPrev = 0; }; }