Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Dialog for setting customizable game options
  4. //
  5. //=============================================================================//
  6. #include "sessionoptionsdialog.h"
  7. #include "engine/imatchmaking.h"
  8. #include "EngineInterface.h"
  9. #include "vgui_controls/ImagePanel.h"
  10. #include "vgui_controls/Label.h"
  11. #include "KeyValues.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. //---------------------------------------------------------------------
  15. // CSessionOptionsDialog
  16. //---------------------------------------------------------------------
  17. CSessionOptionsDialog::CSessionOptionsDialog( vgui::Panel *pParent ) : BaseClass( pParent, "SessionOptions" )
  18. {
  19. SetDeleteSelfOnClose( true );
  20. m_pDialogKeys = NULL;
  21. m_bModifySession = false;
  22. }
  23. CSessionOptionsDialog::~CSessionOptionsDialog()
  24. {
  25. m_pScenarioInfos.PurgeAndDeleteElements();
  26. }
  27. //---------------------------------------------------------------------
  28. // Purpose: Dialog keys contain session contexts and properties
  29. //---------------------------------------------------------------------
  30. void CSessionOptionsDialog::SetDialogKeys( KeyValues *pKeys )
  31. {
  32. m_pDialogKeys = pKeys;
  33. }
  34. //---------------------------------------------------------------------
  35. // Purpose: Strip off the game type from the resource name
  36. //---------------------------------------------------------------------
  37. void CSessionOptionsDialog::SetGameType( const char *pString )
  38. {
  39. // Get the full gametype from the resource name
  40. const char *pGametype = Q_stristr( pString, "_" );
  41. if ( !pGametype )
  42. return;
  43. Q_strncpy( m_szGametype, pGametype + 1, sizeof( m_szGametype ) );
  44. // set the menu filter
  45. m_Menu.SetFilter( m_szGametype );
  46. }
  47. //---------------------------------------------------------------------
  48. // Purpose: Center the dialog on the screen
  49. //---------------------------------------------------------------------
  50. void CSessionOptionsDialog::PerformLayout( void )
  51. {
  52. BaseClass::PerformLayout();
  53. UpdateScenarioDisplay();
  54. MoveToCenterOfScreen();
  55. if ( m_pRecommendedLabel )
  56. {
  57. bool bHosting = ( Q_stristr( m_szGametype, "Host" ) );
  58. m_pRecommendedLabel->SetVisible( bHosting );
  59. }
  60. }
  61. //---------------------------------------------------------------------
  62. // Purpose: Apply common properties and contexts
  63. //---------------------------------------------------------------------
  64. void CSessionOptionsDialog::ApplyCommonProperties( KeyValues *pKeys )
  65. {
  66. for ( KeyValues *pProperty = pKeys->GetFirstSubKey(); pProperty != NULL; pProperty = pProperty->GetNextKey() )
  67. {
  68. const char *pName = pProperty->GetName();
  69. if ( !Q_stricmp( pName, "SessionContext" ) )
  70. {
  71. // Create a new session context
  72. sessionProperty_t ctx;
  73. ctx.nType = SESSION_CONTEXT;
  74. Q_strncpy( ctx.szID, pProperty->GetString( "id", "NULL" ), sizeof( ctx.szID ) );
  75. Q_strncpy( ctx.szValue, pProperty->GetString( "value", "NULL" ), sizeof( ctx.szValue ) );
  76. // ctx.szValueType not used
  77. m_SessionProperties.AddToTail( ctx );
  78. }
  79. else if ( !Q_stricmp( pName, "SessionProperty" ) )
  80. {
  81. // Create a new session property
  82. sessionProperty_t prop;
  83. prop.nType = SESSION_PROPERTY;
  84. Q_strncpy( prop.szID, pProperty->GetString( "id", "NULL" ), sizeof( prop.szID ) );
  85. Q_strncpy( prop.szValue, pProperty->GetString( "value", "NULL" ), sizeof( prop.szValue ) );
  86. Q_strncpy( prop.szValueType, pProperty->GetString( "valuetype", "NULL" ), sizeof( prop.szValueType ) );
  87. m_SessionProperties.AddToTail( prop );
  88. }
  89. else if ( !Q_stricmp( pName, "SessionFlag" ) )
  90. {
  91. sessionProperty_t flag;
  92. flag.nType = SESSION_FLAG;
  93. Q_strncpy( flag.szID, pProperty->GetString(), sizeof( flag.szID ) );
  94. m_SessionProperties.AddToTail( flag );
  95. }
  96. }
  97. }
  98. //---------------------------------------------------------------------
  99. // Purpose: Parse session properties and contexts from the resource file
  100. //---------------------------------------------------------------------
  101. void CSessionOptionsDialog::ApplySettings( KeyValues *pResourceData )
  102. {
  103. BaseClass::ApplySettings( pResourceData );
  104. // Apply settings common to all game types
  105. ApplyCommonProperties( pResourceData );
  106. // Apply settings specific to this game type
  107. KeyValues *pSettings = pResourceData->FindKey( m_szGametype );
  108. if ( pSettings )
  109. {
  110. Q_strncpy( m_szCommand, pSettings->GetString( "commandstring", "NULL" ), sizeof( m_szCommand ) );
  111. m_pTitle->SetText( pSettings->GetString( "title", "Unknown" ) );
  112. ApplyCommonProperties( pSettings );
  113. }
  114. KeyValues *pScenarios = pResourceData->FindKey( "ScenarioInfoPanels" );
  115. if ( pScenarios )
  116. {
  117. for ( KeyValues *pScenario = pScenarios->GetFirstSubKey(); pScenario != NULL; pScenario = pScenario->GetNextKey() )
  118. {
  119. CScenarioInfoPanel *pScenarioInfo = new CScenarioInfoPanel( this, "ScenarioInfoPanel" );
  120. SETUP_PANEL( pScenarioInfo );
  121. pScenarioInfo->m_pTitle->SetText( pScenario->GetString( "title" ) );
  122. pScenarioInfo->m_pSubtitle->SetText( pScenario->GetString( "subtitle" ) );
  123. pScenarioInfo->m_pMapImage->SetImage( pScenario->GetString( "image" ) );
  124. int nTall = pScenario->GetInt( "tall", -1 );
  125. if ( nTall > 0 )
  126. {
  127. pScenarioInfo->SetTall( nTall );
  128. }
  129. m_pScenarioInfos.AddToTail( pScenarioInfo );
  130. }
  131. }
  132. if ( Q_stristr( m_szGametype, "Modify" ) )
  133. {
  134. m_bModifySession = true;
  135. }
  136. }
  137. int CSessionOptionsDialog::GetMaxPlayersRecommendedOption( void )
  138. {
  139. MM_QOS_t qos = matchmaking->GetQosWithLIVE();
  140. // Conservatively assume that every player needs ~ 7 kBytes/s
  141. // plus one for the hosting player.
  142. int numPlayersCanService = 1 + int( qos.flBwUpKbs / 7.0f );
  143. // Determine the option that suits our B/W bests
  144. int options[] = { 8, 12, 16 };
  145. for ( int k = 1; k < ARRAYSIZE( options ); ++ k )
  146. {
  147. if ( options[k] > numPlayersCanService )
  148. {
  149. Msg( "[SessionOptionsDialog] Defaulting number of players to %d (upstream b/w = %.1f kB/s ~ %d players).\n",
  150. options[k - 1], qos.flBwUpKbs, numPlayersCanService );
  151. return k - 1;
  152. }
  153. }
  154. return ARRAYSIZE( options ) - 1;
  155. }
  156. //---------------------------------------------------------------------
  157. // Purpose: Set up colors and other such stuff
  158. //---------------------------------------------------------------------
  159. void CSessionOptionsDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  160. {
  161. BaseClass::ApplySchemeSettings( pScheme );
  162. for ( int i = 0; i < m_pScenarioInfos.Count(); ++i )
  163. {
  164. m_pScenarioInfos[i]->SetBgColor( pScheme->GetColor( "TanDark", Color( 0, 0, 0, 255 ) ) );
  165. }
  166. m_pRecommendedLabel = dynamic_cast<vgui::Label *>(FindChildByName( "RecommendedLabel" ));
  167. }
  168. //---------------------------------------------------------------------
  169. // Purpose: Send all properties and contexts to the matchmaking session
  170. //---------------------------------------------------------------------
  171. void CSessionOptionsDialog::SetupSession( void )
  172. {
  173. KeyValues *pKeys = new KeyValues( "SessionKeys" );
  174. // Send user-selected properties and contexts
  175. for ( int i = 0; i < m_Menu.GetItemCount(); ++i )
  176. {
  177. COptionsItem *pItem = dynamic_cast< COptionsItem* >( m_Menu.GetItem( i ) );
  178. if ( !pItem )
  179. {
  180. continue;
  181. }
  182. const sessionProperty_t &prop = pItem->GetActiveOption();
  183. KeyValues *pProperty = pKeys->CreateNewKey();
  184. pProperty->SetName( prop.szID );
  185. pProperty->SetInt( "type", prop.nType );
  186. pProperty->SetString( "valuestring", prop.szValue );
  187. pProperty->SetString( "valuetype", prop.szValueType );
  188. pProperty->SetInt( "optionindex", pItem->GetActiveOptionIndex() );
  189. }
  190. // Send contexts and properties parsed from the resource file
  191. for ( int i = 0; i < m_SessionProperties.Count(); ++i )
  192. {
  193. const sessionProperty_t &prop = m_SessionProperties[i];
  194. KeyValues *pProperty = pKeys->CreateNewKey();
  195. pProperty->SetName( prop.szID );
  196. pProperty->SetInt( "type", prop.nType );
  197. pProperty->SetString( "valuestring", prop.szValue );
  198. pProperty->SetString( "valuetype", prop.szValueType );
  199. }
  200. // Matchmaking will make a copy of these keys
  201. matchmaking->SetSessionProperties( pKeys );
  202. pKeys->deleteThis();
  203. }
  204. //-----------------------------------------------------------------
  205. // Purpose: Show the correct scenario image and text
  206. //-----------------------------------------------------------------
  207. void CSessionOptionsDialog::UpdateScenarioDisplay( void )
  208. {
  209. // Check if the selected map has changed (first menu item)
  210. int idx = m_Menu.GetActiveOptionIndex( 0 );
  211. for ( int i = 0; i < m_pScenarioInfos.Count(); ++i )
  212. {
  213. m_pScenarioInfos[i]->SetVisible( i == idx );
  214. }
  215. }
  216. //-----------------------------------------------------------------
  217. // Purpose:
  218. //-----------------------------------------------------------------
  219. void CSessionOptionsDialog::OnMenuItemChanged( KeyValues *pData )
  220. {
  221. // which item changed
  222. int iItem = pData->GetInt( "item", -1 );
  223. if ( iItem >= 0 && iItem < m_Menu.GetItemCount() )
  224. {
  225. COptionsItem *pActiveOption = dynamic_cast< COptionsItem* >( m_Menu.GetItem( iItem ) );
  226. if ( pActiveOption )
  227. {
  228. const sessionProperty_t &activeOption = pActiveOption->GetActiveOption();
  229. if ( !Q_strncmp( activeOption.szID, "PROPERTY_GAME_SIZE", sessionProperty_t::MAX_KEY_LEN ) )
  230. {
  231. // make sure the private slots is less than prop.szValue
  232. int iMaxPlayers = atoi(activeOption.szValue);
  233. bool bShouldWarnMaxPlayers = ( pActiveOption->GetActiveOptionIndex() > GetMaxPlayersRecommendedOption() );
  234. m_pRecommendedLabel->SetVisible( bShouldWarnMaxPlayers );
  235. // find the private slots option and repopulate it
  236. for ( int iMenu = 0; iMenu < m_Menu.GetItemCount(); ++iMenu )
  237. {
  238. COptionsItem *pItem = dynamic_cast< COptionsItem* >( m_Menu.GetItem( iMenu ) );
  239. if ( !pItem )
  240. {
  241. continue;
  242. }
  243. const sessionProperty_t &prop = pItem->GetActiveOption();
  244. if ( !Q_strncmp( prop.szID, "PROPERTY_PRIVATE_SLOTS", sessionProperty_t::MAX_KEY_LEN ) )
  245. {
  246. const sessionProperty_t baseProp = pItem->GetActiveOption();
  247. // preserve the selection
  248. int iActiveItem = pItem->GetActiveOptionIndex();
  249. // clear all options
  250. pItem->DeleteAllOptions();
  251. // re-add the items 0 - maxplayers
  252. int nStart = 0;
  253. int nEnd = iMaxPlayers;
  254. int nInterval = 1;
  255. for ( int i = nStart; i <= nEnd; i += nInterval )
  256. {
  257. sessionProperty_t propNew;
  258. propNew.nType = SESSION_PROPERTY;
  259. Q_strncpy( propNew.szID, baseProp.szID, sizeof( propNew.szID ) );
  260. Q_strncpy( propNew.szValueType, baseProp.szValueType, sizeof( propNew.szValueType ) );
  261. Q_snprintf( propNew.szValue, sizeof( propNew.szValue), "%d", i );
  262. pItem->AddOption( propNew.szValue, propNew );
  263. }
  264. // re-set the focus
  265. pItem->SetOptionFocus( min( iActiveItem, iMaxPlayers ) );
  266. // fixup the option sizes
  267. m_Menu.InvalidateLayout();
  268. }
  269. }
  270. }
  271. }
  272. }
  273. }
  274. //-----------------------------------------------------------------
  275. // Purpose: Change properties of a menu item
  276. //-----------------------------------------------------------------
  277. void CSessionOptionsDialog::OverrideMenuItem( KeyValues *pItemKeys )
  278. {
  279. if ( m_bModifySession && m_pDialogKeys )
  280. {
  281. if ( !Q_stricmp( pItemKeys->GetName(), "OptionsItem" ) )
  282. {
  283. const char *pID = pItemKeys->GetString( "id", "NULL" );
  284. KeyValues *pKey = m_pDialogKeys->FindKey( pID );
  285. if ( pKey )
  286. {
  287. pItemKeys->SetInt( "activeoption", pKey->GetInt( "optionindex" ) );
  288. }
  289. }
  290. }
  291. //
  292. // When hosting a new session on LIVE:
  293. // - restrict max number of players to bandwidth allowed
  294. //
  295. if ( !m_bModifySession &&
  296. ( !Q_stricmp( m_szGametype, "hoststandard" ) || !Q_stricmp( m_szGametype, "hostranked" ) )
  297. )
  298. {
  299. if ( !Q_stricmp( pItemKeys->GetName(), "OptionsItem" ) )
  300. {
  301. const char *pID = pItemKeys->GetString( "id", "NULL" );
  302. if ( !Q_stricmp( pID, "PROPERTY_GAME_SIZE" ) )
  303. {
  304. pItemKeys->SetInt( "activeoption", GetMaxPlayersRecommendedOption() );
  305. }
  306. }
  307. }
  308. }
  309. //-----------------------------------------------------------------
  310. // Purpose: Send key presses to the dialog's menu
  311. //-----------------------------------------------------------------
  312. void CSessionOptionsDialog::OnKeyCodePressed( vgui::KeyCode code )
  313. {
  314. if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
  315. {
  316. OnCommand( m_szCommand );
  317. }
  318. else if ( (code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B) && m_bModifySession )
  319. {
  320. // Return to the session lobby without making any changes
  321. OnCommand( "DialogClosing" );
  322. }
  323. else
  324. {
  325. BaseClass::OnKeyCodePressed( code );
  326. }
  327. UpdateScenarioDisplay();
  328. }
  329. //-----------------------------------------------------------------------------
  330. // Purpose:
  331. //-----------------------------------------------------------------------------
  332. void CSessionOptionsDialog::OnThink()
  333. {
  334. vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
  335. if ( code )
  336. {
  337. m_Menu.HandleKeyCode( code );
  338. UpdateScenarioDisplay();
  339. }
  340. BaseClass::OnThink();
  341. }
  342. //---------------------------------------------------------------------
  343. // Purpose: Handle menu commands
  344. //---------------------------------------------------------------------
  345. void CSessionOptionsDialog::OnCommand( const char *pCommand )
  346. {
  347. // Don't set up the session if the dialog is just closing
  348. if ( Q_stricmp( pCommand, "DialogClosing" ) )
  349. {
  350. SetupSession();
  351. OnClose();
  352. }
  353. GetParent()->OnCommand( pCommand );
  354. }