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.

532 lines
15 KiB

  1. #include "cbase.h"
  2. #include "tf_warinfopanel.h"
  3. #include "econ_controls.h"
  4. #include "econ_item_inventory.h"
  5. #include "vgui_avatarimage.h"
  6. #include "vgui_controls/ProgressBar.h"
  7. #include <vgui_controls/HTML.h>
  8. #include "c_tf_player.h"
  9. #include "econ_ui.h"
  10. #include "tf_asyncpanel.h"
  11. #include "item_model_panel.h"
  12. #include "tf_wardata.h"
  13. #include "iclientmode.h"
  14. #include <vgui_controls/AnimationController.h>
  15. #include "vgui/ISystem.h"
  16. #ifdef STAGING_ONLY
  17. ConVar tf_war_override_active_state( "tf_war_override_active_state", "-1" );
  18. #endif
  19. bool IsWarActive( war_definition_index_t nDefIndex )
  20. {
  21. #ifdef STAGING_ONLY
  22. if ( tf_war_override_active_state.GetInt() != -1 )
  23. {
  24. if ( tf_war_override_active_state.GetInt() == 0 )
  25. return false;
  26. return true;
  27. }
  28. #endif
  29. const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nDefIndex );
  30. Assert( pWarDef );
  31. if ( !pWarDef )
  32. return false;
  33. return pWarDef->IsActive();
  34. }
  35. template < typename T >
  36. void SetNestedDialogVariable( Panel *pRoot, const char *pszPanel, const char *pszVariable, T val )
  37. {
  38. EditablePanel *pPanel = pRoot->FindControl<EditablePanel>( pszPanel, true );
  39. if ( pPanel )
  40. {
  41. pPanel->SetDialogVariable( pszVariable, val );
  42. }
  43. }
  44. DECLARE_BUILD_FACTORY( CWarStandingPanel );
  45. CWarStandingPanel::CWarStandingPanel( Panel* pParent, const char* pszPanelName )
  46. : BaseClass( pParent, pszPanelName )
  47. , m_flLastUpdateTime( 0.f )
  48. , m_pTeam0ProgressBar( NULL )
  49. , m_pTeam1ProgressBar( NULL )
  50. , m_bNeedsLerp( false )
  51. {
  52. ListenForGameEvent( "global_war_data_updated" );
  53. }
  54. void CWarStandingPanel::ApplySchemeSettings( IScheme *pScheme )
  55. {
  56. BaseClass::ApplySchemeSettings( pScheme );
  57. LoadControlSettings( "Resource/UI/econ/WarStandingPanel.res" );
  58. for( int i=0; i<2; ++i )
  59. {
  60. m_Scores[i].m_pTeamProgressBar = FindControl< ContinuousProgressBar >( CFmtStr( "Team%dProgressBar", i ), true );
  61. m_Scores[i].m_pContainerPanel = FindControl< EditablePanel >( CFmtStr( "Team%dContainer", i ), true );
  62. m_Scores[i].m_pScoreLabel = FindControl< CExLabel >( CFmtStr( "Team%dScore", i ), true );
  63. m_Scores[i].m_pTeamLabel = FindControl< CExLabel >( CFmtStr( "Team%dName", i ), true );
  64. }
  65. }
  66. void CWarStandingPanel::ApplySettings( KeyValues *inResourceData )
  67. {
  68. BaseClass::ApplySettings( inResourceData );
  69. m_strWarName = inResourceData->GetString( "war_name", NULL );
  70. }
  71. void CWarStandingPanel::OnThink()
  72. {
  73. BaseClass::OnThink();
  74. if ( Plat_FloatTime() - m_flLastUpdateTime > 30.f )
  75. {
  76. InvalidateLayout();
  77. }
  78. if ( m_bNeedsLerp )
  79. {
  80. int nTotal = 0;
  81. if ( m_Scores[ 0 ].m_nNewScore != 0 || m_Scores[ 1 ].m_nNewScore != 0 )
  82. {
  83. nTotal = m_Scores[ 0 ].m_nNewScore + m_Scores[ 1 ].m_nNewScore;
  84. }
  85. float flPercent = GetPercentAnimated();
  86. for( int i=0; i<2; ++i )
  87. {
  88. // Lerp flPercent from [0,1] -> [StartScore,EndScore]
  89. float flCurrentScore = RemapValClamped( flPercent, 0.f, 1.f, (float)m_Scores[i].m_nLastScore, (float)m_Scores[i].m_nNewScore );
  90. float flProgressPercent = 0.f;
  91. if ( nTotal != 0 )
  92. {
  93. flProgressPercent = flCurrentScore / (float)nTotal;
  94. }
  95. m_Scores[i].m_pTeamProgressBar->SetProgress( flProgressPercent );
  96. m_Scores[i].m_pContainerPanel->SetDialogVariable( CFmtStr( "team%dscore", i ), CFmtStr( "%.0f%%", flProgressPercent * 100.f ) );
  97. int nScoreXpos = RemapValClamped( flProgressPercent
  98. , 0.f
  99. , 1.f
  100. , m_Scores[i].m_pTeamProgressBar->GetXPos()
  101. , m_Scores[i].m_pTeamProgressBar->GetXPos() + m_Scores[i].m_pTeamProgressBar->GetWide() );
  102. m_Scores[i].m_pScoreLabel->SetPos( nScoreXpos, m_Scores[i].m_pScoreLabel->GetYPos() );
  103. }
  104. if ( flPercent == 1.f )
  105. {
  106. m_bNeedsLerp = false;
  107. }
  108. }
  109. }
  110. void CWarStandingPanel::PerformLayout()
  111. {
  112. BaseClass::PerformLayout();
  113. if ( GetPercentAnimated() == 1.f )
  114. {
  115. m_flLastUpdateTime = Plat_FloatTime();
  116. }
  117. const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByName( m_strWarName );
  118. Assert( pWarDef->GetSides().Count() == 2 ); // Needs to be 2 sides to the war for this panel to work!
  119. CWarData *pWarData = GetLocalPlayerWarData( pWarDef->GetDefIndex() );
  120. war_side_t nAffiliation = INVALID_WAR_SIDE;
  121. if ( pWarData )
  122. {
  123. nAffiliation = pWarData->Obj().affiliation();
  124. }
  125. FOR_EACH_MAP_FAST( pWarDef->GetSides(), i )
  126. {
  127. uint64 nScore = nScore = GetWarData().GetGlobalSideScore( pWarDef->GetDefIndex(), pWarDef->GetSide( i )->m_nSideIndex );
  128. m_Scores[ i ].m_nLastScore = m_Scores[ i ].m_nNewScore;
  129. m_Scores[ i ].m_nNewScore = nScore;
  130. if ( m_Scores[ i ].m_pTeamLabel )
  131. {
  132. m_Scores[ i ].m_pTeamLabel->SetText( pWarDef->GetSide( i )->m_pszLocalizedName );
  133. }
  134. // Check if there's a change to show
  135. m_bNeedsLerp |= m_Scores[ i ].m_nLastScore != m_Scores[ i ].m_nNewScore;
  136. if ( !m_bNeedsLerp )
  137. {
  138. m_Scores[i].m_pContainerPanel->SetDialogVariable( CFmtStr( "team%dscore", i ), CFmtStr( "%.0f%%", m_Scores[i].m_pTeamProgressBar->GetProgress() * 100.f ) );
  139. }
  140. // Set the "(Your side)" labels
  141. SetControlVisible( CFmtStr( "Team%dYourSide", i ), i == nAffiliation, true );
  142. }
  143. }
  144. float CWarStandingPanel::GetPercentAnimated() const
  145. {
  146. float flTimeSinceLastUpdate = Plat_FloatTime() - m_flLastUpdateTime;
  147. float flPercent = Bias( RemapValClamped( flTimeSinceLastUpdate, 0.f, 1.2f, 0.f, 1.f ), 0.8f );
  148. return flPercent;
  149. }
  150. void CWarStandingPanel::FireGameEvent( IGameEvent *event )
  151. {
  152. if ( FStrEq( event->GetName(), "global_war_data_updated" ) )
  153. {
  154. InvalidateLayout();
  155. }
  156. }
  157. DECLARE_BUILD_FACTORY( CWarLandingPanel );
  158. CWarLandingPanel::CWarLandingPanel( Panel *pParent, const char *pszPanelName )
  159. : BaseClass( pParent, pszPanelName )
  160. , m_flChoseTeamTime( 0.f )
  161. , m_nLastKnownSide( INVALID_WAR_SIDE )
  162. , m_nPendingSide( INVALID_WAR_SIDE )
  163. , m_eJoiningState( NO_ACTION )
  164. {
  165. }
  166. void CWarLandingPanel::ApplySchemeSettings( IScheme *pScheme )
  167. {
  168. BaseClass::ApplySchemeSettings( pScheme );
  169. KeyValues* pKVConditions = new KeyValues( "conditions" );
  170. if ( IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) )
  171. {
  172. pKVConditions->AddSubKey( new KeyValues( "if_war_active" ) );
  173. }
  174. else
  175. {
  176. pKVConditions->AddSubKey( new KeyValues( "if_war_over" ) );
  177. }
  178. LoadControlSettings( "Resource/UI/econ/WarJoinPanel.res", NULL, NULL, pKVConditions );
  179. }
  180. void CWarLandingPanel::ApplySettings( KeyValues *inResourceData )
  181. {
  182. BaseClass::ApplySettings( inResourceData );
  183. m_strSceneAnimName = inResourceData->GetString( "scene_anim_name", NULL );
  184. Assert( m_strSceneAnimName.Length() );
  185. }
  186. void CWarLandingPanel::OnCommand( const char *pCommand )
  187. {
  188. if ( FStrEq( pCommand, "close" ) )
  189. {
  190. if ( m_eJoiningState == NO_ACTION )
  191. {
  192. SetVisible( false );
  193. }
  194. }
  195. else if ( V_strncasecmp( pCommand, "join_war", V_strlen( "join_war" ) ) == 0 )
  196. {
  197. int nSide = V_atoi( &pCommand[ V_strlen( "join_war" ) ] );
  198. const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( PYRO_VS_HEAVY_WAR_DEF_INDEX );
  199. if ( !IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) )
  200. {
  201. DevMsg( "War is inactive" );
  202. return;
  203. }
  204. if ( !pWarDef->IsValidSide( nSide ) )
  205. {
  206. DevMsg( "%d is not a valid side", nSide );
  207. return;
  208. }
  209. m_nPendingSide = nSide;
  210. Assert( m_eJoiningState == NO_ACTION );
  211. m_eJoiningState = CONFIRM_SIDE_SELECTION;
  212. UpdateUIState();
  213. return;
  214. }
  215. else if ( FStrEq( pCommand, "confirm_team" ) )
  216. {
  217. if ( !IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) )
  218. {
  219. DevMsg( "War is inactive" );
  220. return;
  221. }
  222. // Join the war!
  223. GCSDK::CProtoBufMsg< CGCMsgGC_War_JoinWar > msg( k_EMsgGC_War_JoinWar );
  224. msg.Body().set_affiliation( m_nPendingSide );
  225. msg.Body().set_war_id( PYRO_VS_HEAVY_WAR_DEF_INDEX );
  226. GCClientSystem()->BSendMessage( msg );
  227. m_flChoseTeamTime = Plat_FloatTime();
  228. m_eJoiningState = ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE;
  229. UpdateUIState();
  230. return;
  231. }
  232. else if ( FStrEq( pCommand, "dismiss_joining_result" ) )
  233. {
  234. m_eJoiningState = NO_ACTION;
  235. m_nPendingSide = INVALID_WAR_SIDE;
  236. UpdateUIState();
  237. return;
  238. }
  239. else if ( FStrEq( "view_update_comic", pCommand ) )
  240. {
  241. const char* pszComicURL = "http://www.teamfortress.com/theshowdown/";
  242. if ( steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->IsOverlayEnabled() )
  243. {
  244. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( pszComicURL );
  245. }
  246. else
  247. {
  248. vgui::system()->ShellExecute( "open", pszComicURL );
  249. }
  250. return;
  251. }
  252. BaseClass::OnCommand( pCommand );
  253. }
  254. void CWarLandingPanel::OnThink()
  255. {
  256. BaseClass::OnThink();
  257. if ( ( Plat_FloatTime() - m_flChoseTeamTime ) > 5.f && m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE )
  258. {
  259. m_eJoiningState = FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION;
  260. UpdateUIState();
  261. }
  262. }
  263. #ifdef STAGING_ONLY
  264. // 12345? Yea, well, we probably will never have 12,345 sides to a war
  265. ConVar tf_fake_war_side( "tf_fake_war_side", "12345" );
  266. #endif
  267. void CWarLandingPanel::PerformLayout()
  268. {
  269. BaseClass::PerformLayout();
  270. #ifdef STAGING_ONLY
  271. if ( tf_fake_war_side.GetInt() != 12345 )
  272. {
  273. m_nLastKnownSide = tf_fake_war_side.GetInt();
  274. }
  275. #endif
  276. UpdateUIState();
  277. }
  278. void CWarLandingPanel::UpdateWarStatus( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
  279. {
  280. if ( pObject->GetTypeID() != CWarData::k_nTypeID )
  281. {
  282. return;
  283. }
  284. if ( !steamapicontext || !steamapicontext->SteamUser() )
  285. {
  286. return;
  287. }
  288. // Make sure this is for us
  289. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  290. if ( steamIDOwner != steamID )
  291. {
  292. return;
  293. }
  294. const CWarData* pWarData = assert_cast< const CWarData* >( pObject );
  295. if ( pWarData->Obj().affiliation() != m_nLastKnownSide )
  296. {
  297. if ( m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE )
  298. {
  299. m_eJoiningState = SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION;
  300. }
  301. m_nLastKnownSide = pWarData->Obj().affiliation();
  302. }
  303. UpdateUIState();
  304. }
  305. void CWarLandingPanel::SetVisible( bool bVisible )
  306. {
  307. BaseClass::SetVisible( bVisible );
  308. EditablePanel* pSceneContainer = FindControl< EditablePanel >( "SceneContainer", true );
  309. if ( pSceneContainer )
  310. {
  311. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pSceneContainer, m_strSceneAnimName, false );
  312. }
  313. }
  314. void CWarLandingPanel::UpdateUIState()
  315. {
  316. const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( PYRO_VS_HEAVY_WAR_DEF_INDEX ); // SLAM
  317. if ( !pWarDef )
  318. return;
  319. EditablePanel* pMainContainer = FindControl< EditablePanel >( "MainContainer" );
  320. if ( pMainContainer )
  321. {
  322. bool bWarActive = IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX );
  323. pMainContainer->SetControlVisible( "WarOverContainer", !bWarActive );
  324. bool bNeedsToJoin = m_nLastKnownSide == INVALID_WAR_SIDE;
  325. pMainContainer->SetControlVisible( "AffiliatedContainer", bWarActive && !bNeedsToJoin && m_eJoiningState == NO_ACTION );
  326. pMainContainer->SetControlVisible( "UnaffiliatedContainer", bWarActive && bNeedsToJoin && m_eJoiningState == NO_ACTION );
  327. bool bHasGCConnection = GCClientSystem()->BConnectedtoGC();
  328. pMainContainer->SetControlVisible( "JoinPyroButton", bHasGCConnection, true );
  329. pMainContainer->SetControlVisible( "JoinHeavyButton", bHasGCConnection, true );
  330. pMainContainer->SetControlVisible( "NoGContainer", !bHasGCConnection, true );
  331. }
  332. static wchar_t wszEndDateOutString[ 128 ];
  333. char time_buf[k_RTimeRenderBufferSize];
  334. CRTime timeExpire( pWarDef->GetEndDate() );
  335. timeExpire.SetToGMT( false );
  336. timeExpire.Render( time_buf );
  337. wchar_t wszTemp[ 1024 ];
  338. V_UTF8ToUnicode( time_buf, wszTemp, sizeof(wszTemp) );
  339. const wchar_t *wpszEndDateFormat = g_pVGuiLocalize->Find( IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) ? "#TF_War_EndFutureDate" : "#TF_War_EndPastDate" );
  340. g_pVGuiLocalize->ConstructString_safe( wszEndDateOutString, wpszEndDateFormat, 1, wszTemp );
  341. CExLabel* pSceneContainer = FindControl< CExLabel >( "EndDateLabel", true );
  342. if ( pSceneContainer )
  343. {
  344. pSceneContainer->SetText( wszEndDateOutString );
  345. }
  346. EditablePanel* pJoiningPopup = FindControl< EditablePanel >( "CommunicatingWithGCPopup", true );
  347. if ( !pJoiningPopup )
  348. return;
  349. switch ( m_eJoiningState )
  350. {
  351. case NO_ACTION:
  352. break;
  353. case CONFIRM_SIDE_SELECTION:
  354. {
  355. Assert( m_nPendingSide != INVALID_WAR_SIDE );
  356. const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nPendingSide ); // Can be NULL
  357. Assert( pChosenSide );
  358. if ( !pChosenSide )
  359. return;
  360. static wchar_t wszOutString[ 128 ];
  361. const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_ConfirmSideSelection" );
  362. g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) );
  363. EditablePanel* pJoining = FindControl< EditablePanel >( "ConfirmSelectionContainer", true );
  364. if ( pJoining )
  365. {
  366. pJoining->SetDialogVariable( "confirm_selection", wszOutString );
  367. }
  368. }
  369. break;
  370. case ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE:
  371. {
  372. Assert( m_nPendingSide != INVALID_WAR_SIDE );
  373. const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nPendingSide ); // Can be NULL
  374. Assert( pChosenSide );
  375. if ( !pChosenSide )
  376. return;
  377. static wchar_t wszOutString[ 128 ];
  378. const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_JoiningTeam" );
  379. g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) );
  380. EditablePanel* pJoining = FindControl< EditablePanel >( "JoiningContainer", true );
  381. if ( pJoining )
  382. {
  383. pJoining->SetDialogVariable( "joining_team", wszOutString );
  384. }
  385. }
  386. break;
  387. case SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION:
  388. {
  389. const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nLastKnownSide ); // Can be NULL
  390. Assert( pChosenSide );
  391. if ( !pChosenSide )
  392. return;
  393. static wchar_t wszOutString[ 128 ];
  394. const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_JoinedTeam" );
  395. g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) );
  396. EditablePanel* pJoined = FindControl< EditablePanel >( "TeamJoinedContainer", true );
  397. if ( pJoined )
  398. {
  399. pJoined->SetDialogVariable( "team_joined", wszOutString );
  400. }
  401. }
  402. break;
  403. case FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION:
  404. break;
  405. default:
  406. Assert( false );
  407. return;
  408. }
  409. pJoiningPopup->SetVisible( m_eJoiningState != NO_ACTION );
  410. pJoiningPopup->SetControlVisible( "ConfirmSelectionContainer", m_eJoiningState == CONFIRM_SIDE_SELECTION, true );
  411. pJoiningPopup->SetControlVisible( "JoiningContainer", m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE, true );
  412. pJoiningPopup->SetControlVisible( "TeamJoinedContainer", m_eJoiningState == SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, true );
  413. pJoiningPopup->SetControlVisible( "FailedToJoinContainer", m_eJoiningState == FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, true );
  414. }
  415. #if defined( STAGING_ONLY )
  416. CON_COMMAND( tf_war_join_side, "Join a specified war on a specified side" )
  417. {
  418. if ( args.ArgC() < 2 )
  419. return;
  420. war_definition_index_t nWar = atoi( args[1] );
  421. const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nWar );
  422. if ( pWarDef == NULL)
  423. return;
  424. war_side_t nSide = atoi( args[2] );
  425. // Allow INVALID_WAR_SIDE for testing purposes
  426. if ( pWarDef->GetSide( nSide ) == NULL && nSide != INVALID_WAR_SIDE )
  427. return;
  428. // Join the war!
  429. GCSDK::CProtoBufMsg< CGCMsgGC_War_JoinWar > msg( k_EMsgGC_War_JoinWar );
  430. msg.Body().set_war_id( nWar );
  431. msg.Body().set_affiliation( nSide );
  432. GCClientSystem()->BSendMessage( msg );
  433. }
  434. #endif