game_menu entity and associated CHudMenu changes

This commit is contained in:
ALLEN-PC\acj30 2025-02-13 09:00:19 -06:00 committed by Blixibon
parent 985ebc0dbb
commit cdafc1278e
6 changed files with 771 additions and 20 deletions

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -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" );

View File

@ -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<CBasePlayer*>( 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<CGameMenu*>( 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<CBasePlayer*>(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

View File

@ -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<EHANDLE> m_ActivePlayers;
CUtlVector<float> 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

View File

@ -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<CBasePlayer*>(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<CGameMenu*>( IGameMenuAutoList::AutoList()[i] );
if ( pMenu->IsActiveOnTarget( pEdict ) )
{
pMenu->MenuSelected( slot, pEdict );
return true;
}
}
}
}
#endif
}
return false;

View File

@ -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();