You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
13 KiB
420 lines
13 KiB
//========= 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 ); |
|
} |
|
|
|
|