From cdafc1278ebd367483dc67d1242390972ec70a18 Mon Sep 17 00:00:00 2001 From: "ALLEN-PC\\acj30" Date: Thu, 13 Feb 2025 09:00:19 -0600 Subject: [PATCH] game_menu entity and associated CHudMenu changes --- sp/src/game/client/menu.cpp | 255 +++++++++- sp/src/game/client/menu.h | 13 + sp/src/game/server/maprules.cpp | 447 ++++++++++++++++++ sp/src/game/server/maprules.h | 48 ++ sp/src/game/shared/gamerules.cpp | 24 + .../shared/mapbase/mapbase_usermessages.cpp | 4 + 6 files changed, 771 insertions(+), 20 deletions(-) diff --git a/sp/src/game/client/menu.cpp b/sp/src/game/client/menu.cpp index f5ee3169..b11eb43b 100644 --- a/sp/src/game/client/menu.cpp +++ b/sp/src/game/client/menu.cpp @@ -37,6 +37,9 @@ char g_szPrelocalisedMenuString[MAX_MENU_STRING]; DECLARE_HUDELEMENT( CHudMenu ); DECLARE_HUD_MESSAGE( CHudMenu, ShowMenu ); +#ifdef MAPBASE +DECLARE_HUD_MESSAGE( CHudMenu, ShowMenuComplex ); +#endif // //----------------------------------------------------- @@ -71,6 +74,9 @@ CHudMenu::CHudMenu( const char *pElementName ) : void CHudMenu::Init( void ) { HOOK_HUD_MESSAGE( CHudMenu, ShowMenu ); +#ifdef MAPBASE + HOOK_HUD_MESSAGE( CHudMenu, ShowMenuComplex ); +#endif m_bMenuTakesInput = false; m_bMenuDisplayed = false; @@ -81,6 +87,24 @@ void CHudMenu::Init( void ) Reset(); } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudMenu::LevelInit() +{ + CHudElement::LevelInit(); + +#ifdef MAPBASE + if (m_bMapDefinedMenu) + { + // Fixes menu retaining on level change/reload + // TODO: Would non-map menus benefit from this as well? + m_bMenuTakesInput = false; + m_bMenuDisplayed = false; + } +#endif +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -114,7 +138,11 @@ void CHudMenu::OnThink() float flSelectionTimeout = MENU_SELECTION_TIMEOUT; // If we've been open for a while without input, hide +#ifdef MAPBASE + if ( m_bMenuDisplayed && ( gpGlobals->curtime - m_flSelectionTime > flSelectionTimeout && !m_bMapDefinedMenu ) ) +#else if ( m_bMenuDisplayed && ( gpGlobals->curtime - m_flSelectionTime > flSelectionTimeout ) ) +#endif { m_bMenuDisplayed = false; } @@ -130,11 +158,24 @@ bool CHudMenu::ShouldDraw( void ) return false; // check for if menu is set to disappear - if ( m_flShutoffTime > 0 && m_flShutoffTime <= gpGlobals->realtime ) + if ( m_flShutoffTime > 0 ) { - // times up, shutoff - m_bMenuDisplayed = false; - return false; +#ifdef MAPBASE + if ( m_bMapDefinedMenu && !m_bPlayingFadeout && (m_flShutoffTime - m_flOpenCloseTime) <= GetMenuTime() ) + { + // Begin closing the menu + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MenuClose" ); + m_bMenuTakesInput = false; + m_bPlayingFadeout = true; + } + else +#endif + if ( m_flShutoffTime <= GetMenuTime() ) + { + // times up, shutoff + m_bMenuDisplayed = false; + return false; + } } return draw; @@ -169,23 +210,21 @@ void CHudMenu::Paint() return; // center it - int x = 20; + int x = m_nBorder; Color menuColor = m_MenuColor; Color itemColor = m_ItemColor; int c = m_Processed.Count(); - int border = 20; - - int wide = m_nMaxPixels + border; - int tall = m_nHeight + border; + int wide = m_nMaxPixels + m_nBorder; + int tall = m_nHeight + m_nBorder; int y = ( ScreenHeight() - tall ) * 0.5f; - DrawBox( x - border/2, y - border/2, wide, tall, m_BoxColor, m_flSelectionAlphaOverride / 255.0f ); + DrawBox( x - m_nBorder/2, y - m_nBorder/2, wide, tall, m_BoxColor, m_flSelectionAlphaOverride / 255.0f ); - //DrawTexturedBox( x - border/2, y - border/2, wide, tall, m_BoxColor, m_flSelectionAlphaOverride / 255.0f ); + //DrawTexturedBox( x - m_nBorder/2, y - m_nBorder/2, wide, tall, m_BoxColor, m_flSelectionAlphaOverride / 255.0f ); menuColor[3] = menuColor[3] * ( m_flSelectionAlphaOverride / 255.0f ); itemColor[3] = itemColor[3] * ( m_flSelectionAlphaOverride / 255.0f ); @@ -195,7 +234,18 @@ void CHudMenu::Paint() ProcessedLine *line = &m_Processed[ i ]; Assert( line ); - Color clr = line->menuitem != 0 ? itemColor : menuColor; +#ifdef MAPBASE + bool isItem = true; + if (line->menuitem == 0 && line->startchar < (MAX_MENU_STRING-1) && g_szMenuString[ line->startchar ] != L'0' && g_szMenuString[ line->startchar+1 ] != L'.') + { + // Can't use 0 directly because it gets conflated with the cancel item + isItem = false; + } +#else + bool isItem = line->menuitem != 0; +#endif + + Color clr = isItem ? itemColor : menuColor; bool canblur = false; if ( line->menuitem != 0 && @@ -208,15 +258,15 @@ void CHudMenu::Paint() vgui::surface()->DrawSetTextColor( clr ); int drawLen = line->length; - if ( line->menuitem != 0 ) + if (isItem) { drawLen *= m_flTextScan; } - vgui::surface()->DrawSetTextFont( line->menuitem != 0 ? m_hItemFont : m_hTextFont ); + vgui::surface()->DrawSetTextFont( isItem ? m_hItemFont : m_hTextFont ); PaintString( &g_szMenuString[ line->startchar ], drawLen, - line->menuitem != 0 ? m_hItemFont : m_hTextFont, x, y ); + isItem ? m_hItemFont : m_hTextFont, x, y ); if ( canblur ) { @@ -242,6 +292,20 @@ void CHudMenu::Paint() } } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline float CHudMenu::GetMenuTime( void ) +{ +#ifdef MAPBASE + // In singleplayer, use the curtime instead. This fixes issues with menus disappearing after pausing + if (gpGlobals->maxClients <= 1) + return gpGlobals->curtime; +#endif + + return gpGlobals->realtime; +} + //----------------------------------------------------------------------------- // Purpose: selects an item from the menu //----------------------------------------------------------------------------- @@ -260,7 +324,7 @@ void CHudMenu::SelectMenuItem( int menu_item ) // remove the menu quickly m_bMenuTakesInput = false; - m_flShutoffTime = gpGlobals->realtime + m_flOpenCloseTime; + m_flShutoffTime = GetMenuTime() + m_flOpenCloseTime; g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("MenuClose"); } } @@ -339,9 +403,20 @@ void CHudMenu::ProcessText( void ) { ProcessedLine *l = &m_Processed[ i ]; Assert( l ); + +#ifdef MAPBASE + bool isItem = true; + if (l->menuitem == 0 && l->startchar < (MAX_MENU_STRING-1) && g_szMenuString[ l->startchar ] != L'0' && g_szMenuString[ l->startchar+1 ] != L'.') + { + // Can't use 0 directly because it gets conflated with the cancel item + isItem = false; + } +#else + bool isItem = l->menuitem != 0; +#endif int pixels = 0; - vgui::HFont font = l->menuitem != 0 ? m_hItemFont : m_hTextFont; + vgui::HFont font = isItem ? m_hItemFont : m_hTextFont; for ( int ch = 0; ch < l->length; ch++ ) { @@ -364,7 +439,7 @@ void CHudMenu::ProcessText( void ) void CHudMenu::HideMenu( void ) { m_bMenuTakesInput = false; - m_flShutoffTime = gpGlobals->realtime + m_flOpenCloseTime; + m_flShutoffTime = GetMenuTime() + m_flOpenCloseTime; g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("MenuClose"); } @@ -381,6 +456,11 @@ void CHudMenu::ShowMenu( const char * menuName, int validSlots ) m_flShutoffTime = -1; m_bitsValidSlots = validSlots; m_fWaitingForMore = 0; + m_nBorder = 20; +#ifdef MAPBASE + m_bMapDefinedMenu = false; + m_bPlayingFadeout = false; +#endif Q_strncpy( g_szPrelocalisedMenuString, menuName, sizeof( g_szPrelocalisedMenuString ) ); @@ -408,6 +488,11 @@ void CHudMenu::ShowMenu_KeyValueItems( KeyValues *pKV ) m_flShutoffTime = -1; m_fWaitingForMore = 0; m_bitsValidSlots = 0; + m_nBorder = 20; +#ifdef MAPBASE + m_bMapDefinedMenu = false; + m_bPlayingFadeout = false; +#endif g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("MenuOpen"); m_nSelectedItem = -1; @@ -426,7 +511,11 @@ void CHudMenu::ShowMenu_KeyValueItems( KeyValues *pKV ) const char *pszItem = item->GetName(); const wchar_t *wLocalizedItem = g_pVGuiLocalize->Find( pszItem ); +#ifdef MAPBASE + nCount = _snwprintf( pWritePosition, nRemaining, L"->%d. %ls\n", i+1, wLocalizedItem ); +#else nCount = _snwprintf( pWritePosition, nRemaining, L"%d. %ls\n", i+1, wLocalizedItem ); +#endif nRemaining -= nCount; pWritePosition += nCount; @@ -436,7 +525,11 @@ void CHudMenu::ShowMenu_KeyValueItems( KeyValues *pKV ) // put a cancel on the end m_bitsValidSlots |= (1<<9); +#ifdef MAPBASE + nCount = _snwprintf( pWritePosition, nRemaining, L"->0. %ls\n", g_pVGuiLocalize->Find( "#Cancel" ) ); +#else nCount = _snwprintf( pWritePosition, nRemaining, L"0. %ls\n", g_pVGuiLocalize->Find( "#Cancel" ) ); +#endif nRemaining -= nCount; pWritePosition += nCount; @@ -465,8 +558,7 @@ void CHudMenu::MsgFunc_ShowMenu( bf_read &msg) if ( DisplayTime > 0 ) { - m_flShutoffTime = m_flOpenCloseTime + DisplayTime + gpGlobals->realtime; - + m_flShutoffTime = m_flOpenCloseTime + DisplayTime + GetMenuTime(); } else { @@ -511,8 +603,131 @@ void CHudMenu::MsgFunc_ShowMenu( bf_read &msg) } m_fWaitingForMore = NeedMore; + m_nBorder = 20; +#ifdef MAPBASE + m_bMapDefinedMenu = false; + m_bPlayingFadeout = false; +#endif } +#ifdef MAPBASE +ConVar hud_menu_complex_border( "hud_menu_complex_border", "30" ); + +//----------------------------------------------------------------------------- +// Purpose: Message handler for ShowMenu message with more options for game_menu +// takes four values: +// short : a bitfield of keys that are valid input +// float : the duration, in seconds, the menu should stay up. -1 means it stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, false if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +//----------------------------------------------------------------------------- +void CHudMenu::MsgFunc_ShowMenuComplex( bf_read &msg) +{ + m_bitsValidSlots = (short)msg.ReadWord(); + float DisplayTime = msg.ReadFloat(); + int NeedMore = msg.ReadByte(); + + m_nBorder = hud_menu_complex_border.GetInt(); + m_bMapDefinedMenu = true; + m_bPlayingFadeout = false; + + if ( DisplayTime > 0 ) + { + m_flShutoffTime = m_flOpenCloseTime + DisplayTime + GetMenuTime(); + } + else + { + m_flShutoffTime = -1; + } + + if ( m_bitsValidSlots > -1 ) + { + char szString[2048]; + msg.ReadString( szString, sizeof(szString) ); + + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + Q_strncpy( g_szPrelocalisedMenuString, szString, sizeof( g_szPrelocalisedMenuString ) ); + } + else + { // append to the current menu string + Q_strncat( g_szPrelocalisedMenuString, szString, sizeof( g_szPrelocalisedMenuString ), COPY_ALL_CHARACTERS ); + } + + if ( !NeedMore ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("MenuOpenFlash"); + m_nSelectedItem = -1; + + // we have the whole string, so we can localise it now + wchar_t *pWritePosition = g_szMenuString; + int nRemaining = sizeof( g_szMenuString ) / sizeof( wchar_t ); + int nCount; + + char *pszToken = strtok( szString, "\n" ); + int nCurItem = 0; + for (; pszToken != NULL; pszToken = strtok( NULL, "\n" ), nCurItem++) + { + if (!*pszToken || *pszToken == ' ') + continue; + + wchar_t wszMenuItem[128]; + + const wchar_t *wLocalizedItem = g_pVGuiLocalize->Find( pszToken ); + if (wLocalizedItem) + { + V_wcsncpy( wszMenuItem, wLocalizedItem, sizeof( wszMenuItem ) ); + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( pszToken, wszMenuItem, sizeof( wszMenuItem ) ); + } + + if (nCurItem == 0) + { + // First item is title + nCount = _snwprintf( pWritePosition, nRemaining, L"%ls\n", wszMenuItem ); + } + else + { + // If this item isn't valid, skip until it is + //while (!(m_bitsValidSlots & (1 << nCurItem)) && nCurItem < 10) + //{ + // nCurItem++; + //} + + if (nCurItem == 10) + nCurItem = 0; + + nCount = _snwprintf( pWritePosition, nRemaining, L"->%d. %ls\n", nCurItem, wszMenuItem ); + } + + nRemaining -= nCount; + pWritePosition += nCount; + } + + ProcessText(); + } + + m_bMenuDisplayed = true; + + if (m_bitsValidSlots > 0) + m_bMenuTakesInput = true; + else + m_bMenuTakesInput = false; + + m_flSelectionTime = gpGlobals->curtime; + } + else + { + HideMenu(); + } + + m_fWaitingForMore = NeedMore; +} +#endif + //----------------------------------------------------------------------------- // Purpose: hud scheme settings //----------------------------------------------------------------------------- diff --git a/sp/src/game/client/menu.h b/sp/src/game/client/menu.h index 0b3ae9a7..80f14eae 100644 --- a/sp/src/game/client/menu.h +++ b/sp/src/game/client/menu.h @@ -26,10 +26,14 @@ class CHudMenu : public CHudElement, public vgui::Panel public: CHudMenu( const char *pElementName ); void Init( void ); + void LevelInit( void ); void VidInit( void ); void Reset( void ); virtual bool ShouldDraw( void ); void MsgFunc_ShowMenu( bf_read &msg ); +#ifdef MAPBASE + void MsgFunc_ShowMenuComplex( bf_read &msg ); +#endif void HideMenu( void ); void ShowMenu( const char * menuName, int keySlot ); void ShowMenu_KeyValueItems( KeyValues *pKV ); @@ -42,6 +46,8 @@ private: virtual void Paint(); virtual void ApplySchemeSettings(vgui::IScheme *pScheme); private: + float GetMenuTime( void ); + void ProcessText( void ); void PaintString( const wchar_t *text, int textlen, vgui::HFont& font, int x, int y ); @@ -59,6 +65,7 @@ private: int m_nMaxPixels; int m_nHeight; + int m_nBorder; bool m_bMenuDisplayed; int m_bitsValidSlots; @@ -69,6 +76,12 @@ private: float m_flSelectionTime; +#ifdef MAPBASE + // Indicates this menu is defined by game_menu + bool m_bMapDefinedMenu; + bool m_bPlayingFadeout; +#endif + CPanelAnimationVar( float, m_flOpenCloseTime, "OpenCloseTime", "1" ); CPanelAnimationVar( float, m_flBlur, "Blur", "0" ); diff --git a/sp/src/game/server/maprules.cpp b/sp/src/game/server/maprules.cpp index 60279244..7422ed83 100644 --- a/sp/src/game/server/maprules.cpp +++ b/sp/src/game/server/maprules.cpp @@ -14,6 +14,7 @@ #include "entityoutput.h" #ifdef MAPBASE #include "eventqueue.h" +#include "saverestore_utlvector.h" #endif // memdbgon must be the last include file in a .cpp file!!! @@ -825,3 +826,449 @@ void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TY } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Displays a custom number menu for player(s) +//----------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( game_menu, CGameMenu ); + +BEGIN_DATADESC( CGameMenu ) + + DEFINE_UTLVECTOR( m_ActivePlayers, FIELD_EHANDLE ), + DEFINE_UTLVECTOR( m_ActivePlayerTimes, FIELD_TIME ), + + DEFINE_KEYFIELD( m_flDisplayTime, FIELD_FLOAT, "holdtime" ), + + DEFINE_KEYFIELD( m_iszTitle, FIELD_STRING, "Title" ), + + DEFINE_KEYFIELD( m_iszOption[0], FIELD_STRING, "Case01" ), + DEFINE_KEYFIELD( m_iszOption[1], FIELD_STRING, "Case02" ), + DEFINE_KEYFIELD( m_iszOption[2], FIELD_STRING, "Case03" ), + DEFINE_KEYFIELD( m_iszOption[3], FIELD_STRING, "Case04" ), + DEFINE_KEYFIELD( m_iszOption[4], FIELD_STRING, "Case05" ), + DEFINE_KEYFIELD( m_iszOption[5], FIELD_STRING, "Case06" ), + DEFINE_KEYFIELD( m_iszOption[6], FIELD_STRING, "Case07" ), + DEFINE_KEYFIELD( m_iszOption[7], FIELD_STRING, "Case08" ), + DEFINE_KEYFIELD( m_iszOption[8], FIELD_STRING, "Case09" ), + DEFINE_KEYFIELD( m_iszOption[9], FIELD_STRING, "Case10" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "ShowMenu", InputShowMenu ), + DEFINE_INPUTFUNC( FIELD_VOID, "HideMenu", InputHideMenu ), + DEFINE_INPUTFUNC( FIELD_VOID, "__DoRestore", InputDoRestore ), + + // Outputs + DEFINE_OUTPUT( m_OnCase[0], "OnCase01" ), + DEFINE_OUTPUT( m_OnCase[1], "OnCase02" ), + DEFINE_OUTPUT( m_OnCase[2], "OnCase03" ), + DEFINE_OUTPUT( m_OnCase[3], "OnCase04" ), + DEFINE_OUTPUT( m_OnCase[4], "OnCase05" ), + DEFINE_OUTPUT( m_OnCase[5], "OnCase06" ), + DEFINE_OUTPUT( m_OnCase[6], "OnCase07" ), + DEFINE_OUTPUT( m_OnCase[7], "OnCase08" ), + DEFINE_OUTPUT( m_OnCase[8], "OnCase09" ), + DEFINE_OUTPUT( m_OnCase[9], "OnCase10" ), + DEFINE_OUTPUT( m_OutValue, "OutValue" ), + DEFINE_OUTPUT( m_OnTimeout, "OnTimeout" ), + + DEFINE_THINKFUNC( TimeoutThink ), + +END_DATADESC() + +IMPLEMENT_AUTO_LIST( IGameMenuAutoList ); + +static const char *s_pTimeoutContext = "TimeoutContext"; + +#define MENU_INFINITE_TIME -1.0f + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGameMenu::CGameMenu() +{ + m_flDisplayTime = 5.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::OnRestore() +{ + BaseClass::OnRestore(); + + // Do this a bit after we restore since the HUD might not be ready yet + g_EventQueue.AddEvent( this, "__DoRestore", 0.4f, this, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::InputDoRestore( inputdata_t &inputdata ) +{ + // Check if we should restore the menu on anyone + FOR_EACH_VEC_BACK( m_ActivePlayers, i ) + { + if (m_ActivePlayers[i].Get()) + { + if (m_ActivePlayerTimes[i] > gpGlobals->curtime || m_ActivePlayerTimes[i] == MENU_INFINITE_TIME) + { + CRecipientFilter filter; + filter.AddRecipient( static_cast( m_ActivePlayers[i].Get() ) ); + + ShowMenu( filter, m_ActivePlayerTimes[i] - gpGlobals->curtime ); + continue; + } + } + + // Remove this player since it's no longer valid + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::TimeoutThink() +{ + float flNextLowestTime = FLT_MAX; + FOR_EACH_VEC( m_ActivePlayerTimes, i ) + { + // If the player is still in our list, then they must not have selected an option + if (m_ActivePlayerTimes[i] != MENU_INFINITE_TIME) + { + if (m_ActivePlayerTimes[i] <= gpGlobals->curtime) + { + m_OnTimeout.FireOutput( m_ActivePlayers[i], this ); + + // Remove this player since it's no longer valid + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + break; + } + else if (m_ActivePlayerTimes[i] < flNextLowestTime) + { + flNextLowestTime = m_ActivePlayerTimes[i]; + } + } + } + + if (flNextLowestTime < FLT_MAX) + { + SetNextThink( flNextLowestTime, s_pTimeoutContext ); + } + else + { + SetContextThink( NULL, TICK_NEVER_THINK, s_pTimeoutContext ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::ShowMenu( CRecipientFilter &filter, float flDisplayTime ) +{ + // Before showing the menu, check each menu to see if there's already one being shown to one of our recipients + for ( int i = 0; i < IGameMenuAutoList::AutoList().Count(); i++ ) + { + CGameMenu *pMenu = static_cast( IGameMenuAutoList::AutoList()[i] ); + if ( pMenu != this && pMenu->IsActive() ) + { + for ( int j = 0; j < filter.GetRecipientCount(); j++ ) + { + CBaseEntity *ent = CBaseEntity::Instance( filter.GetRecipientIndex( j ) ); + if ( pMenu->IsActiveOnTarget( ent ) ) + { + Msg( "%s overriding menu %s for player %i\n", GetDebugName(), pMenu->GetDebugName(), j ); + pMenu->RemoveTarget( ent ); + } + } + } + } + + if (flDisplayTime == 0.0f) + { + flDisplayTime = m_flDisplayTime; + } + + char szString[512] = { 0 }; + int nBitsValidSlots = 0; + + if (m_iszTitle != NULL_STRING) + { + V_strncat( szString, STRING( m_iszTitle ), sizeof( szString ) ); + } + else + { + // Insert space to tell menu code to skip + V_strncat( szString, " ", sizeof( szString ) ); + } + + // Insert newline even if there's no string + V_strncat( szString, "\n", sizeof( szString ) ); + + // Populate the options + for (int i = 0; i < MAX_MENU_OPTIONS; i++) + { + if (m_iszOption[i] != NULL_STRING) + { + nBitsValidSlots |= (1 << i); + + V_strncat( szString, STRING( m_iszOption[i] ), sizeof( szString ) ); + } + else + { + // Insert space to tell menu code to skip + V_strncat( szString, " ", sizeof( szString ) ); + } + + // Insert newline even if there's no string + V_strncat( szString, "\n", sizeof( szString ) ); + } + + if (nBitsValidSlots <= 0 && m_iszTitle == NULL_STRING) + { + Warning( "%s ShowMenu: Can't show menu with no options or title\n", GetDebugName() ); + return; + } + + UserMessageBegin( filter, "ShowMenuComplex" ); + WRITE_WORD( nBitsValidSlots ); + WRITE_FLOAT( flDisplayTime ); + WRITE_BYTE( 0 ); + WRITE_STRING( szString ); + MessageEnd(); + + float flMenuTime; + if (flDisplayTime <= 0.0f) + { + flMenuTime = MENU_INFINITE_TIME; + } + else + { + flMenuTime = gpGlobals->curtime + flDisplayTime; + } + + for ( int j = 0; j < filter.GetRecipientCount(); j++ ) + { + CBaseEntity *ent = CBaseEntity::Instance( filter.GetRecipientIndex( j ) ); + + // Check if we already track this player. If not, make a new one + bool bFound = false; + FOR_EACH_VEC( m_ActivePlayers, i ) + { + if (m_ActivePlayers[i].Get() == ent) + { + m_ActivePlayerTimes[i] = flMenuTime; + bFound = true; + break; + } + } + + if (!bFound) + { + m_ActivePlayers.AddToTail( ent ); + m_ActivePlayerTimes.AddToTail( flMenuTime ); + } + } + + if (GetNextThink( s_pTimeoutContext ) == TICK_NEVER_THINK) + { + SetContextThink( &CGameMenu::TimeoutThink, gpGlobals->curtime + flDisplayTime, s_pTimeoutContext ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::HideMenu( CRecipientFilter &filter ) +{ + UserMessageBegin( filter, "ShowMenuComplex" ); + WRITE_WORD( -1 ); + WRITE_FLOAT( 0.0f ); + WRITE_BYTE( 0 ); + WRITE_STRING( "" ); + MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::MenuSelected( int nSlot, CBaseEntity *pActivator ) +{ + if (nSlot <= 0 || nSlot > MAX_MENU_OPTIONS) + { + Warning( "%s: Invalid slot %i\n", GetDebugName(), nSlot ); + return; + } + + m_OnCase[nSlot-1].FireOutput( pActivator, this ); + m_OutValue.Set( nSlot, pActivator, this ); + + RemoveTarget( pActivator ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CGameMenu::IsActive() +{ + FOR_EACH_VEC_BACK( m_ActivePlayers, i ) + { + if (m_ActivePlayers[i].Get()) + { + if (m_ActivePlayerTimes[i] > gpGlobals->curtime || m_ActivePlayerTimes[i] == MENU_INFINITE_TIME) + return true; + } + + // Remove this player since it's no longer valid + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CGameMenu::IsActiveOnTarget( CBaseEntity *pPlayer ) +{ + FOR_EACH_VEC_BACK( m_ActivePlayers, i ) + { + if (m_ActivePlayers[i].Get() == pPlayer) + { + if (m_ActivePlayerTimes[i] > gpGlobals->curtime || m_ActivePlayerTimes[i] == MENU_INFINITE_TIME) + return true; + + // Remove this player since it's no longer valid + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + return false; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::RemoveTarget( CBaseEntity *pPlayer ) +{ + FOR_EACH_VEC_BACK( m_ActivePlayers, i ) + { + if (m_ActivePlayers[i].Get() == pPlayer) + { + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::InputShowMenu( inputdata_t &inputdata ) +{ + if (HasSpawnFlags( SF_GAMEMENU_ALLPLAYERS )) + { + CRecipientFilter filter; + filter.AddAllPlayers(); + + ShowMenu( filter ); + } + else + { + CBasePlayer *pPlayer = NULL; + + // If we're in singleplayer, show the message to the player. + if ( gpGlobals->maxClients == 1 ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + // Otherwise show the message to the player that triggered us. + else if ( inputdata.pActivator && inputdata.pActivator->IsNetClient() ) + { + pPlayer = ToBasePlayer( inputdata.pActivator ); + } + + if (pPlayer) + { + CRecipientFilter filter; + filter.AddRecipient( pPlayer ); + + ShowMenu( filter ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGameMenu::InputHideMenu( inputdata_t &inputdata ) +{ + if (HasSpawnFlags( SF_GAMEMENU_ALLPLAYERS )) + { + CRecipientFilter filter; + + FOR_EACH_VEC_BACK( m_ActivePlayers, i ) + { + // Select all players in our list who are still active, and remove them + if (m_ActivePlayerTimes[i] > gpGlobals->curtime || m_ActivePlayerTimes[i] == MENU_INFINITE_TIME) + { + filter.AddRecipient( static_cast(m_ActivePlayers[i].Get()) ); + } + + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + } + + if (filter.GetRecipientCount() <= 0) + return; + + HideMenu( filter ); + } + else + { + CBasePlayer *pPlayer = NULL; + + // If we're in singleplayer, show the message to the player. + if ( gpGlobals->maxClients == 1 ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + // Otherwise show the message to the player that triggered us. + else if ( inputdata.pActivator && inputdata.pActivator->IsNetClient() ) + { + pPlayer = ToBasePlayer( inputdata.pActivator ); + } + + if (!pPlayer) + return; + + // Verify that this player is in our list + CRecipientFilter filter; + FOR_EACH_VEC( m_ActivePlayers, i ) + { + if (m_ActivePlayers[i].Get() == pPlayer && (m_ActivePlayerTimes[i] > gpGlobals->curtime || m_ActivePlayerTimes[i] == MENU_INFINITE_TIME)) + { + filter.AddRecipient( pPlayer ); + + // Remove since the player won't have the menu anymore + m_ActivePlayers.Remove( i ); + m_ActivePlayerTimes.Remove( i ); + break; + } + } + + if (filter.GetRecipientCount() <= 0) + return; + + HideMenu( filter ); + } +} +#endif + + diff --git a/sp/src/game/server/maprules.h b/sp/src/game/server/maprules.h index 7a4b1553..38b9e6dc 100644 --- a/sp/src/game/server/maprules.h +++ b/sp/src/game/server/maprules.h @@ -9,7 +9,55 @@ #ifndef MAPRULES_H #define MAPRULES_H +#ifdef MAPBASE +#define MAX_MENU_OPTIONS 10 +#define SF_GAMEMENU_ALLPLAYERS 0x0001 + +//----------------------------------------------------------------------------- +// Purpose: Displays a custom number menu for player(s) +//----------------------------------------------------------------------------- +DECLARE_AUTO_LIST( IGameMenuAutoList ); +class CGameMenu : public CLogicalEntity, public IGameMenuAutoList +{ +public: + DECLARE_CLASS( CGameMenu, CLogicalEntity ); + DECLARE_DATADESC(); + CGameMenu(); + + void OnRestore(); + void InputDoRestore( inputdata_t &inputdata ); + + void TimeoutThink(); + + void ShowMenu( CRecipientFilter &filter, float flDisplayTime = 0.0f ); + void HideMenu( CRecipientFilter &filter ); + void MenuSelected( int nSlot, CBaseEntity *pActivator ); + + bool IsActive(); + bool IsActiveOnTarget( CBaseEntity *pPlayer ); + void RemoveTarget( CBaseEntity *pPlayer ); + + // Inputs + void InputShowMenu( inputdata_t &inputdata ); + void InputHideMenu( inputdata_t &inputdata ); + +private: + + CUtlVector m_ActivePlayers; + CUtlVector m_ActivePlayerTimes; + + float m_flDisplayTime; + + string_t m_iszTitle; + string_t m_iszOption[MAX_MENU_OPTIONS]; + + // Outputs + COutputEvent m_OnCase[MAX_MENU_OPTIONS]; // Fired for the option chosen + COutputInt m_OutValue; // Outputs the option chosen + COutputEvent m_OnTimeout; // Fires when no option was chosen in time +}; +#endif #endif // MAPRULES_H diff --git a/sp/src/game/shared/gamerules.cpp b/sp/src/game/shared/gamerules.cpp index face9d54..82f7428a 100644 --- a/sp/src/game/shared/gamerules.cpp +++ b/sp/src/game/shared/gamerules.cpp @@ -27,6 +27,9 @@ #include "player_resource.h" #include "tactical_mission.h" #include "gamestats.h" +#ifdef MAPBASE + #include "maprules.h" +#endif #endif @@ -619,6 +622,27 @@ bool CGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) { if( GetVoiceGameMgr()->ClientCommand( static_cast(pEdict), args ) ) return true; + +#ifdef MAPBASE + if ( FStrEq( args[0], "menuselect" ) ) + { + if ( args.ArgC() >= 2 ) + { + int slot = atoi( args[1] ); + + // See if this is from a game_menu + for ( int i = 0; i < IGameMenuAutoList::AutoList().Count(); i++ ) + { + CGameMenu *pMenu = static_cast( IGameMenuAutoList::AutoList()[i] ); + if ( pMenu->IsActiveOnTarget( pEdict ) ) + { + pMenu->MenuSelected( slot, pEdict ); + return true; + } + } + } + } +#endif } return false; diff --git a/sp/src/game/shared/mapbase/mapbase_usermessages.cpp b/sp/src/game/shared/mapbase/mapbase_usermessages.cpp index 5af946e6..f0a71fd4 100644 --- a/sp/src/game/shared/mapbase/mapbase_usermessages.cpp +++ b/sp/src/game/shared/mapbase/mapbase_usermessages.cpp @@ -20,6 +20,8 @@ void HookMapbaseUserMessages( void ) { // VScript //HOOK_MESSAGE( ScriptMsg ); // Hooked in CNetMsgScriptHelper + + //HOOK_MESSAGE( ShowMenuComplex ); // Hooked in CHudMenu } #endif @@ -28,6 +30,8 @@ void RegisterMapbaseUserMessages( void ) // VScript usermessages->Register( "ScriptMsg", -1 ); // CNetMsgScriptHelper + usermessages->Register( "ShowMenuComplex", -1 ); // CHudMenu + #ifdef CLIENT_DLL // TODO: Better placement? HookMapbaseUserMessages();