diff options
Diffstat (limited to 'gameui/matchmaking/sessionoptionsdialog.cpp')
| -rw-r--r-- | gameui/matchmaking/sessionoptionsdialog.cpp | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/gameui/matchmaking/sessionoptionsdialog.cpp b/gameui/matchmaking/sessionoptionsdialog.cpp new file mode 100644 index 0000000..fba64d8 --- /dev/null +++ b/gameui/matchmaking/sessionoptionsdialog.cpp @@ -0,0 +1,420 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Dialog for setting customizable game options +// +//=============================================================================// + +#include "sessionoptionsdialog.h" +#include "engine/imatchmaking.h" +#include "EngineInterface.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/Label.h" +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//--------------------------------------------------------------------- +// CSessionOptionsDialog +//--------------------------------------------------------------------- +CSessionOptionsDialog::CSessionOptionsDialog( vgui::Panel *pParent ) : BaseClass( pParent, "SessionOptions" ) +{ + SetDeleteSelfOnClose( true ); + + m_pDialogKeys = NULL; + m_bModifySession = false; +} + +CSessionOptionsDialog::~CSessionOptionsDialog() +{ + m_pScenarioInfos.PurgeAndDeleteElements(); +} + +//--------------------------------------------------------------------- +// Purpose: Dialog keys contain session contexts and properties +//--------------------------------------------------------------------- +void CSessionOptionsDialog::SetDialogKeys( KeyValues *pKeys ) +{ + m_pDialogKeys = pKeys; +} + + +//--------------------------------------------------------------------- +// Purpose: Strip off the game type from the resource name +//--------------------------------------------------------------------- +void CSessionOptionsDialog::SetGameType( const char *pString ) +{ + // Get the full gametype from the resource name + const char *pGametype = Q_stristr( pString, "_" ); + if ( !pGametype ) + return; + + Q_strncpy( m_szGametype, pGametype + 1, sizeof( m_szGametype ) ); + + // set the menu filter + m_Menu.SetFilter( m_szGametype ); +} + +//--------------------------------------------------------------------- +// Purpose: Center the dialog on the screen +//--------------------------------------------------------------------- +void CSessionOptionsDialog::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + UpdateScenarioDisplay(); + + MoveToCenterOfScreen(); + + if ( m_pRecommendedLabel ) + { + bool bHosting = ( Q_stristr( m_szGametype, "Host" ) ); + m_pRecommendedLabel->SetVisible( bHosting ); + } +} + +//--------------------------------------------------------------------- +// Purpose: Apply common properties and contexts +//--------------------------------------------------------------------- +void CSessionOptionsDialog::ApplyCommonProperties( KeyValues *pKeys ) +{ + for ( KeyValues *pProperty = pKeys->GetFirstSubKey(); pProperty != NULL; pProperty = pProperty->GetNextKey() ) + { + const char *pName = pProperty->GetName(); + + if ( !Q_stricmp( pName, "SessionContext" ) ) + { + // Create a new session context + sessionProperty_t ctx; + ctx.nType = SESSION_CONTEXT; + Q_strncpy( ctx.szID, pProperty->GetString( "id", "NULL" ), sizeof( ctx.szID ) ); + Q_strncpy( ctx.szValue, pProperty->GetString( "value", "NULL" ), sizeof( ctx.szValue ) ); + // ctx.szValueType not used + + m_SessionProperties.AddToTail( ctx ); + } + else if ( !Q_stricmp( pName, "SessionProperty" ) ) + { + // Create a new session property + sessionProperty_t prop; + prop.nType = SESSION_PROPERTY; + Q_strncpy( prop.szID, pProperty->GetString( "id", "NULL" ), sizeof( prop.szID ) ); + Q_strncpy( prop.szValue, pProperty->GetString( "value", "NULL" ), sizeof( prop.szValue ) ); + Q_strncpy( prop.szValueType, pProperty->GetString( "valuetype", "NULL" ), sizeof( prop.szValueType ) ); + + m_SessionProperties.AddToTail( prop ); + } + else if ( !Q_stricmp( pName, "SessionFlag" ) ) + { + sessionProperty_t flag; + flag.nType = SESSION_FLAG; + Q_strncpy( flag.szID, pProperty->GetString(), sizeof( flag.szID ) ); + + m_SessionProperties.AddToTail( flag ); + } + } +} + +//--------------------------------------------------------------------- +// Purpose: Parse session properties and contexts from the resource file +//--------------------------------------------------------------------- +void CSessionOptionsDialog::ApplySettings( KeyValues *pResourceData ) +{ + BaseClass::ApplySettings( pResourceData ); + + // Apply settings common to all game types + ApplyCommonProperties( pResourceData ); + + // Apply settings specific to this game type + KeyValues *pSettings = pResourceData->FindKey( m_szGametype ); + if ( pSettings ) + { + Q_strncpy( m_szCommand, pSettings->GetString( "commandstring", "NULL" ), sizeof( m_szCommand ) ); + m_pTitle->SetText( pSettings->GetString( "title", "Unknown" ) ); + + ApplyCommonProperties( pSettings ); + } + + KeyValues *pScenarios = pResourceData->FindKey( "ScenarioInfoPanels" ); + if ( pScenarios ) + { + for ( KeyValues *pScenario = pScenarios->GetFirstSubKey(); pScenario != NULL; pScenario = pScenario->GetNextKey() ) + { + CScenarioInfoPanel *pScenarioInfo = new CScenarioInfoPanel( this, "ScenarioInfoPanel" ); + SETUP_PANEL( pScenarioInfo ); + pScenarioInfo->m_pTitle->SetText( pScenario->GetString( "title" ) ); + pScenarioInfo->m_pSubtitle->SetText( pScenario->GetString( "subtitle" ) ); + pScenarioInfo->m_pMapImage->SetImage( pScenario->GetString( "image" ) ); + + int nTall = pScenario->GetInt( "tall", -1 ); + if ( nTall > 0 ) + { + pScenarioInfo->SetTall( nTall ); + } + + m_pScenarioInfos.AddToTail( pScenarioInfo ); + } + } + + if ( Q_stristr( m_szGametype, "Modify" ) ) + { + m_bModifySession = true; + } +} + +int CSessionOptionsDialog::GetMaxPlayersRecommendedOption( void ) +{ + MM_QOS_t qos = matchmaking->GetQosWithLIVE(); + + // Conservatively assume that every player needs ~ 7 kBytes/s + // plus one for the hosting player. + int numPlayersCanService = 1 + int( qos.flBwUpKbs / 7.0f ); + + // Determine the option that suits our B/W bests + int options[] = { 8, 12, 16 }; + + for ( int k = 1; k < ARRAYSIZE( options ); ++ k ) + { + if ( options[k] > numPlayersCanService ) + { + Msg( "[SessionOptionsDialog] Defaulting number of players to %d (upstream b/w = %.1f kB/s ~ %d players).\n", + options[k - 1], qos.flBwUpKbs, numPlayersCanService ); + return k - 1; + } + } + + return ARRAYSIZE( options ) - 1; +} + +//--------------------------------------------------------------------- +// Purpose: Set up colors and other such stuff +//--------------------------------------------------------------------- +void CSessionOptionsDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + for ( int i = 0; i < m_pScenarioInfos.Count(); ++i ) + { + m_pScenarioInfos[i]->SetBgColor( pScheme->GetColor( "TanDark", Color( 0, 0, 0, 255 ) ) ); + } + + m_pRecommendedLabel = dynamic_cast<vgui::Label *>(FindChildByName( "RecommendedLabel" )); +} + +//--------------------------------------------------------------------- +// Purpose: Send all properties and contexts to the matchmaking session +//--------------------------------------------------------------------- +void CSessionOptionsDialog::SetupSession( void ) +{ + KeyValues *pKeys = new KeyValues( "SessionKeys" ); + + // Send user-selected properties and contexts + for ( int i = 0; i < m_Menu.GetItemCount(); ++i ) + { + COptionsItem *pItem = dynamic_cast< COptionsItem* >( m_Menu.GetItem( i ) ); + if ( !pItem ) + { + continue; + } + + const sessionProperty_t &prop = pItem->GetActiveOption(); + + KeyValues *pProperty = pKeys->CreateNewKey(); + pProperty->SetName( prop.szID ); + pProperty->SetInt( "type", prop.nType ); + pProperty->SetString( "valuestring", prop.szValue ); + pProperty->SetString( "valuetype", prop.szValueType ); + pProperty->SetInt( "optionindex", pItem->GetActiveOptionIndex() ); + } + + // Send contexts and properties parsed from the resource file + for ( int i = 0; i < m_SessionProperties.Count(); ++i ) + { + const sessionProperty_t &prop = m_SessionProperties[i]; + + KeyValues *pProperty = pKeys->CreateNewKey(); + pProperty->SetName( prop.szID ); + pProperty->SetInt( "type", prop.nType ); + pProperty->SetString( "valuestring", prop.szValue ); + pProperty->SetString( "valuetype", prop.szValueType ); + } + + // Matchmaking will make a copy of these keys + matchmaking->SetSessionProperties( pKeys ); + pKeys->deleteThis(); +} + +//----------------------------------------------------------------- +// Purpose: Show the correct scenario image and text +//----------------------------------------------------------------- +void CSessionOptionsDialog::UpdateScenarioDisplay( void ) +{ + // Check if the selected map has changed (first menu item) + int idx = m_Menu.GetActiveOptionIndex( 0 ); + for ( int i = 0; i < m_pScenarioInfos.Count(); ++i ) + { + m_pScenarioInfos[i]->SetVisible( i == idx ); + } +} + +//----------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------- +void CSessionOptionsDialog::OnMenuItemChanged( KeyValues *pData ) +{ + // which item changed + int iItem = pData->GetInt( "item", -1 ); + + if ( iItem >= 0 && iItem < m_Menu.GetItemCount() ) + { + COptionsItem *pActiveOption = dynamic_cast< COptionsItem* >( m_Menu.GetItem( iItem ) ); + if ( pActiveOption ) + { + const sessionProperty_t &activeOption = pActiveOption->GetActiveOption(); + + if ( !Q_strncmp( activeOption.szID, "PROPERTY_GAME_SIZE", sessionProperty_t::MAX_KEY_LEN ) ) + { + // make sure the private slots is less than prop.szValue + + int iMaxPlayers = atoi(activeOption.szValue); + bool bShouldWarnMaxPlayers = ( pActiveOption->GetActiveOptionIndex() > GetMaxPlayersRecommendedOption() ); + m_pRecommendedLabel->SetVisible( bShouldWarnMaxPlayers ); + + // find the private slots option and repopulate it + for ( int iMenu = 0; iMenu < m_Menu.GetItemCount(); ++iMenu ) + { + COptionsItem *pItem = dynamic_cast< COptionsItem* >( m_Menu.GetItem( iMenu ) ); + if ( !pItem ) + { + continue; + } + + const sessionProperty_t &prop = pItem->GetActiveOption(); + + if ( !Q_strncmp( prop.szID, "PROPERTY_PRIVATE_SLOTS", sessionProperty_t::MAX_KEY_LEN ) ) + { + const sessionProperty_t baseProp = pItem->GetActiveOption(); + + // preserve the selection + int iActiveItem = pItem->GetActiveOptionIndex(); + + // clear all options + pItem->DeleteAllOptions(); + + // re-add the items 0 - maxplayers + int nStart = 0; + int nEnd = iMaxPlayers; + int nInterval = 1; + + for ( int i = nStart; i <= nEnd; i += nInterval ) + { + sessionProperty_t propNew; + propNew.nType = SESSION_PROPERTY; + Q_strncpy( propNew.szID, baseProp.szID, sizeof( propNew.szID ) ); + Q_strncpy( propNew.szValueType, baseProp.szValueType, sizeof( propNew.szValueType ) ); + Q_snprintf( propNew.szValue, sizeof( propNew.szValue), "%d", i ); + pItem->AddOption( propNew.szValue, propNew ); + } + + // re-set the focus + pItem->SetOptionFocus( min( iActiveItem, iMaxPlayers ) ); + + // fixup the option sizes + m_Menu.InvalidateLayout(); + } + } + } + } + } +} + +//----------------------------------------------------------------- +// Purpose: Change properties of a menu item +//----------------------------------------------------------------- +void CSessionOptionsDialog::OverrideMenuItem( KeyValues *pItemKeys ) +{ + if ( m_bModifySession && m_pDialogKeys ) + { + if ( !Q_stricmp( pItemKeys->GetName(), "OptionsItem" ) ) + { + const char *pID = pItemKeys->GetString( "id", "NULL" ); + + KeyValues *pKey = m_pDialogKeys->FindKey( pID ); + if ( pKey ) + { + pItemKeys->SetInt( "activeoption", pKey->GetInt( "optionindex" ) ); + } + } + } + + // + // When hosting a new session on LIVE: + // - restrict max number of players to bandwidth allowed + // + if ( !m_bModifySession && + ( !Q_stricmp( m_szGametype, "hoststandard" ) || !Q_stricmp( m_szGametype, "hostranked" ) ) + ) + { + if ( !Q_stricmp( pItemKeys->GetName(), "OptionsItem" ) ) + { + const char *pID = pItemKeys->GetString( "id", "NULL" ); + if ( !Q_stricmp( pID, "PROPERTY_GAME_SIZE" ) ) + { + pItemKeys->SetInt( "activeoption", GetMaxPlayersRecommendedOption() ); + } + } + } +} + +//----------------------------------------------------------------- +// Purpose: Send key presses to the dialog's menu +//----------------------------------------------------------------- +void CSessionOptionsDialog::OnKeyCodePressed( vgui::KeyCode code ) +{ + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + OnCommand( m_szCommand ); + } + else if ( (code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B) && m_bModifySession ) + { + // Return to the session lobby without making any changes + OnCommand( "DialogClosing" ); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } + + UpdateScenarioDisplay(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSessionOptionsDialog::OnThink() +{ + vgui::KeyCode code = m_KeyRepeat.KeyRepeated(); + if ( code ) + { + m_Menu.HandleKeyCode( code ); + UpdateScenarioDisplay(); + } + + BaseClass::OnThink(); +} + +//--------------------------------------------------------------------- +// Purpose: Handle menu commands +//--------------------------------------------------------------------- +void CSessionOptionsDialog::OnCommand( const char *pCommand ) +{ + // Don't set up the session if the dialog is just closing + if ( Q_stricmp( pCommand, "DialogClosing" ) ) + { + SetupSession(); + OnClose(); + } + + GetParent()->OnCommand( pCommand ); +} + |