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.

878 lines
25 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. // for messaging with the GC
  9. #include "econ_gcmessages.h"
  10. #include "econ_item_inventory.h"
  11. #include "econ_game_account_client.h"
  12. #include "tf_gcmessages.h"
  13. #include "gc_clientsystem.h"
  14. // ui related
  15. #include "ienginevgui.h"
  16. #include "confirm_dialog.h"
  17. #include "tf_controls.h"
  18. #include "econ_notifications.h"
  19. #include "select_player_dialog.h"
  20. #include "vgui_avatarimage.h"
  21. // for hud element
  22. #include "iclientmode.h"
  23. // misc
  24. #include "c_tf_freeaccount.h"
  25. #include "c_tf_player.h"
  26. #include "c_playerresource.h"
  27. #include "tf_hud_statpanel.h"
  28. #include "tf_gamerules.h"
  29. // memdbgon must be the last include file in a .cpp file!!!
  30. #include <tier0/memdbgon.h>
  31. //-----------------------------------------------------------------------------
  32. static bool g_bHadCoach = false;
  33. static bool g_bCanLikeCoach = false;
  34. //-----------------------------------------------------------------------------
  35. // used by the waiting dialogs
  36. class CCoachingWaitDialog : public CGenericWaitingDialog
  37. {
  38. public:
  39. CCoachingWaitDialog() : CGenericWaitingDialog( NULL )
  40. {
  41. }
  42. protected:
  43. virtual void OnTimeout()
  44. {
  45. ShowMessageBox( "#TF_Coach_Timeout_Title", "#TF_Coach_Timeout_Text", "#GameUI_OK" );
  46. }
  47. };
  48. //-----------------------------------------------------------------------------
  49. static bool BInCoachesList()
  50. {
  51. if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() )
  52. {
  53. CEconGameAccountClient *pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton<CEconGameAccountClient>();
  54. if ( pGameAccountClient )
  55. return pGameAccountClient->Obj().in_coaches_list();
  56. }
  57. return false;
  58. }
  59. // send a message to the GC requesting to be added to the list of coaches
  60. static void RequestAddToCoaches()
  61. {
  62. GCSDK::CProtoBufMsg< CMsgTFCoaching_AddToCoaches > msg( k_EMsgGCCoaching_AddToCoaches );
  63. bool bSent = GCClientSystem()->BSendMessage( msg );
  64. if ( bSent )
  65. {
  66. ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_WaitingForServer", true, false, 20.0f );
  67. }
  68. }
  69. // send a message to the GC requesting to be removed from the list of coaches
  70. static void RequestRemoveFromCoaches()
  71. {
  72. GCSDK::CProtoBufMsg< CMsgTFCoaching_RemoveFromCoaches > msg( k_EMsgGCCoaching_RemoveFromCoaches );
  73. GCClientSystem()->BSendMessage( msg );
  74. ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_WaitingForServer", true, false, 20.0f );
  75. }
  76. // alternates between asking to be added/removed from the list of coaches
  77. static void ToggleCoachingConfirm( bool bConfirmed, void *pContext )
  78. {
  79. if ( bConfirmed )
  80. {
  81. if ( BInCoachesList() )
  82. {
  83. RequestRemoveFromCoaches();
  84. }
  85. else
  86. {
  87. RequestAddToCoaches();
  88. }
  89. }
  90. }
  91. static bool IsServerFull()
  92. {
  93. int iNumPlayers = 0;
  94. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  95. {
  96. if ( g_PR->IsConnected( iPlayerIndex ) == false )
  97. continue;
  98. player_info_t pi;
  99. if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
  100. continue;
  101. ++iNumPlayers;
  102. }
  103. return iNumPlayers >= gpGlobals->maxClients;
  104. }
  105. //-----------------------------------------------------------------------------
  106. /**
  107. * Select player to ask to be a coach dialog.
  108. */
  109. class CSelectPlayerForCoachDialog : public CSelectPlayerDialog
  110. {
  111. DECLARE_CLASS_SIMPLE( CSelectPlayerForCoachDialog, CSelectPlayerDialog );
  112. public:
  113. CSelectPlayerForCoachDialog();
  114. virtual ~CSelectPlayerForCoachDialog();
  115. virtual void OnSelectPlayer( const CSteamID &steamID );
  116. virtual void OnCommand( const char *command );
  117. virtual bool AllowOutOfGameFriends() { return false; }
  118. virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
  119. protected:
  120. virtual const char *GetResFile() { return "resource/ui/SelectPlayerDialog_Coach.res"; }
  121. bool CanAskPlayerToCoach( const CSteamID &steamID );
  122. };
  123. static vgui::DHANDLE< CSelectPlayerForCoachDialog > g_pSelectPlayerForCoachDialog;
  124. CSelectPlayerForCoachDialog::CSelectPlayerForCoachDialog()
  125. : CSelectPlayerDialog( NULL )
  126. {
  127. g_pSelectPlayerForCoachDialog = this;
  128. m_bAllowSameTeam = true;
  129. m_bAllowOutsideServer = true;
  130. }
  131. CSelectPlayerForCoachDialog::~CSelectPlayerForCoachDialog()
  132. {
  133. g_pSelectPlayerForCoachDialog = NULL;
  134. }
  135. void CSelectPlayerForCoachDialog::OnSelectPlayer( const CSteamID &steamID )
  136. {
  137. if ( CanAskPlayerToCoach( steamID ) )
  138. {
  139. g_bCanLikeCoach = false;
  140. GCSDK::CProtoBufMsg< CMsgTFCoaching_FindCoach > msg( k_EMsgGCCoaching_FindCoach );
  141. msg.Body().set_account_id_friend_as_coach( steamID.GetAccountID() );
  142. GCClientSystem()->BSendMessage( msg );
  143. ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_AskingFriend", true, false, 20.0f );
  144. }
  145. }
  146. void CSelectPlayerForCoachDialog::OnCommand( const char *command )
  147. {
  148. if ( !Q_stricmp( command, "performmatchmaking" ) )
  149. {
  150. if ( CanAskPlayerToCoach( CSteamID() ) )
  151. {
  152. // close dialog
  153. OnCommand( "cancel" );
  154. // send message
  155. g_bCanLikeCoach = true;
  156. GCSDK::CProtoBufMsg< CMsgTFCoaching_FindCoach > msg( k_EMsgGCCoaching_FindCoach );
  157. GCClientSystem()->BSendMessage( msg );
  158. ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_CoachSearching", true, false, 20.0f );
  159. }
  160. return;
  161. }
  162. BaseClass::OnCommand( command );
  163. }
  164. void CSelectPlayerForCoachDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  165. {
  166. CSelectPlayerDialog::ApplySchemeSettings( pScheme );
  167. SetDialogVariable( "title", g_pVGuiLocalize->Find( "TF_FindCoachDialog_Title" ) );
  168. }
  169. bool CSelectPlayerForCoachDialog::CanAskPlayerToCoach( const CSteamID &steamID )
  170. {
  171. bool bMatchesFriend = false;
  172. const int iMaxPlayers = gpGlobals->maxClients;
  173. int iNumPlayers = 0;
  174. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  175. {
  176. if ( g_PR->IsConnected( iPlayerIndex ) == false )
  177. continue;
  178. player_info_t pi;
  179. if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
  180. continue;
  181. // friend on this server?
  182. if ( pi.friendsID != 0 && pi.friendsID == steamID.GetAccountID() )
  183. {
  184. bMatchesFriend = true;
  185. }
  186. ++iNumPlayers;
  187. }
  188. if ( iMaxPlayers <= iNumPlayers )
  189. {
  190. ShowMessageBox( "#TF_Coach_ServerFull_Title", "#TF_Coach_ServerFull_Text", "#GameUI_OK" );
  191. return false;
  192. }
  193. if ( bMatchesFriend )
  194. {
  195. return true;
  196. }
  197. return steamID.GetAccountID() == 0;
  198. }
  199. static void ShowFindCoachDialog()
  200. {
  201. CSelectPlayerForCoachDialog *pDialog = vgui::SETUP_PANEL( new CSelectPlayerForCoachDialog() );
  202. pDialog->InvalidateLayout( false, true );
  203. pDialog->Reset();
  204. pDialog->SetVisible( true );
  205. pDialog->MakePopup();
  206. pDialog->MoveToFront();
  207. pDialog->SetKeyBoardInputEnabled(true);
  208. pDialog->SetMouseInputEnabled(true);
  209. TFModalStack()->PushModal( pDialog );
  210. }
  211. //-----------------------------------------------------------------------------
  212. // sent from main menu
  213. CON_COMMAND( cl_coach_toggle, "Toggle coach status" )
  214. {
  215. if ( IsFreeTrialAccount() )
  216. {
  217. ShowMessageBox( "#TF_Coach_FreeAccount_Title", "#TF_Coach_FreeAccount_Text", "#GameUI_OK" );
  218. return;
  219. }
  220. if ( BInCoachesList() )
  221. {
  222. ShowConfirmDialog( "#TF_Coach_RemoveCoach_Title", "#TF_Coach_RemoveCoach_Text",
  223. "#TF_Coach_Yes", "#TF_Coach_No",
  224. &ToggleCoachingConfirm );
  225. }
  226. else
  227. {
  228. ShowConfirmDialog( "#TF_Coach_AddCoach_Title", "#TF_Coach_AddCoach_Text",
  229. "#TF_Coach_Yes", "#TF_Coach_No",
  230. &ToggleCoachingConfirm );
  231. }
  232. }
  233. //-----------------------------------------------------------------------------
  234. class CGCCoaching_AddToCoachesResponse : public GCSDK::CGCClientJob
  235. {
  236. public:
  237. CGCCoaching_AddToCoachesResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  238. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  239. {
  240. GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
  241. CloseWaitingDialog();
  242. if ( msg.Body().m_eResponse == k_EGCMsgResponseOK )
  243. {
  244. ShowMessageBox( "#TF_Coach_AddedCoach_Title", "#TF_Coach_AddedCoach_Text", "#GameUI_OK" );
  245. }
  246. else if ( msg.Body().m_eResponse == k_EGCMsgResponseDenied )
  247. {
  248. ShowMessageBox( "#TF_Coach_Denied_Title", "#TF_Coach_Denied_Text", "#GameUI_OK" );
  249. }
  250. return true;
  251. }
  252. };
  253. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_AddToCoachesResponse, "CGCCoaching_AddToCoachesResponse", k_EMsgGCCoaching_AddToCoachesResponse, GCSDK::k_EServerTypeGCClient );
  254. //-----------------------------------------------------------------------------
  255. class CGCCoaching_RemoveFromCoachesResponse : public GCSDK::CGCClientJob
  256. {
  257. public:
  258. CGCCoaching_RemoveFromCoachesResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  259. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  260. {
  261. GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
  262. CloseWaitingDialog();
  263. if ( msg.Body().m_eResponse == k_EGCMsgResponseOK )
  264. {
  265. ShowMessageBox( "#TF_Coach_RemovedCoach_Title", "#TF_Coach_RemovedCoach_Text", "#GameUI_OK" );
  266. }
  267. return true;
  268. }
  269. };
  270. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_RemoveFromCoachesResponse, "CGCCoaching_RemoveFromCoachesResponse", k_EMsgGCCoaching_RemoveFromCoachesResponse, GCSDK::k_EServerTypeGCClient );
  271. //-----------------------------------------------------------------------------
  272. class CGCCoaching_RemovedAsCoach : public GCSDK::CGCClientJob
  273. {
  274. public:
  275. CGCCoaching_RemovedAsCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  276. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  277. {
  278. ShowMessageBox( "#TF_Coach_SessionEnded_Title", "#TF_Coach_SessionEnded_Text", "#GameUI_OK" );
  279. return true;
  280. }
  281. };
  282. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_RemovedAsCoach, "CGCCoaching_RemovedAsCoach", k_EMsgGCCoaching_RemoveCurrentCoach, GCSDK::k_EServerTypeGCClient );
  283. static void FindCoach( bool bConfirmed, void *pContext )
  284. {
  285. if ( bConfirmed )
  286. {
  287. ShowFindCoachDialog();
  288. }
  289. }
  290. static void PromptFindCoach()
  291. {
  292. C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  293. if ( pLocalTFPlayer == NULL )
  294. {
  295. ShowMessageBox( "#TF_Coach_NotInGame_Title", "#TF_Coach_NotInGame_Text", "#GameUI_OK" );
  296. }
  297. else if ( pLocalTFPlayer->m_hCoach != NULL )
  298. {
  299. ShowMessageBox( "#TF_Coach_AlreadyBeingCoached_Title", "#TF_Coach_AlreadyBeingCoached_Text", "#GameUI_OK" );
  300. }
  301. else if ( pLocalTFPlayer->m_hStudent != NULL )
  302. {
  303. ShowMessageBox( "#TF_Coach_AlreadyCoaching_Title", "#TF_Coach_AlreadyCoaching_Text", "#GameUI_OK" );
  304. }
  305. else if ( TFGameRules() && TFGameRules()->IsInTraining() )
  306. {
  307. ShowMessageBox( "#TF_Coach_Training_Title", "#TF_Coach_Training_Text", "#GameUI_OK" );
  308. }
  309. else if ( IsServerFull() )
  310. {
  311. ShowMessageBox( "#TF_Coach_ServerFull_Title", "#TF_Coach_ServerFull_Text", "#GameUI_OK" );
  312. }
  313. else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  314. {
  315. ShowMessageBox( "#TF_Coach_MannVsMachine_Title", "#TF_Coach_MannVsMachine_Text", "#GameUI_OK" );
  316. }
  317. else
  318. {
  319. ShowConfirmDialog( "#TF_Coach_AskStudent_Title", "#TF_Coach_AskStudent_Text",
  320. "#TF_Coach_Yes", "#TF_Coach_No",
  321. &FindCoach );
  322. }
  323. }
  324. CON_COMMAND( cl_coach_find_coach, "Request a coach for the current game" )
  325. {
  326. PromptFindCoach();
  327. }
  328. class CGCCoaching_FindCoachResponse : public GCSDK::CGCClientJob
  329. {
  330. public:
  331. CGCCoaching_FindCoachResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  332. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  333. {
  334. GCSDK::CProtoBufMsg< CMsgTFCoaching_FindCoachResponse > msg( pNetPacket );
  335. CloseWaitingDialog();
  336. if ( msg.Body().found_coach() )
  337. {
  338. const char* pText = "#TF_Coach_FoundCoach_Text";
  339. if ( msg.Body().num_likes() > 2 )
  340. {
  341. pText = "#TF_Coach_FoundCoachLike_Text";
  342. }
  343. CTFMessageBoxDialog *pDialog = ShowMessageBox( "#TF_Coach_FoundCoach_Title", pText, "#GameUI_OK" );
  344. if ( pDialog )
  345. {
  346. wchar_t szPlayerName[MAX_PLAYER_NAME_LENGTH];
  347. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().coach_name().c_str(), szPlayerName, sizeof(szPlayerName) );
  348. pDialog->AddStringToken( "coachname", szPlayerName );
  349. wchar_t szNumLikes[32];
  350. V_snwprintf( szNumLikes, ARRAYSIZE(szNumLikes), L"%d", msg.Body().num_likes() );
  351. pDialog->AddStringToken( "numlikes", szNumLikes );
  352. }
  353. }
  354. else
  355. {
  356. // retry?
  357. ShowConfirmDialog( "#TF_Coach_StudentRetry_Title", "#TF_Coach_StudentRetry_Text",
  358. "#TF_Coach_Yes", "#TF_Coach_No",
  359. &FindCoach );
  360. }
  361. return true;
  362. }
  363. };
  364. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_FindCoachResponse, "CGCCoaching_FindCoachResponse", k_EMsgGCCoaching_FindCoachResponse, GCSDK::k_EServerTypeGCClient );
  365. //-----------------------------------------------------------------------------
  366. class CTFAskCoachNotification : public CEconNotification
  367. {
  368. public:
  369. CTFAskCoachNotification( bool bStudentIsFriend )
  370. : CEconNotification()
  371. , m_bStudentIsFriend( bStudentIsFriend )
  372. {
  373. SetLifetime( 20.0f );
  374. SetText( bStudentIsFriend ? "#TF_Coach_AskCoachForFriend_Text" : "#TF_Coach_AskCoach_Text" );
  375. }
  376. virtual EType NotificationType() { return eType_AcceptDecline; }
  377. // XXX(JohnS): Dead code? This notification type was accept/decline, so how was it being triggered?
  378. virtual void Trigger()
  379. {
  380. // prompt coach
  381. ShowConfirmDialog( "#TF_Coach_AskCoach_Title",
  382. m_bStudentIsFriend ? "#TF_Coach_AskCoachForFriend_Text" : "#TF_Coach_AskCoach_Text",
  383. "#TF_Coach_Yes", "#TF_Coach_No",
  384. &AskCoachCallback );
  385. }
  386. virtual void Accept()
  387. {
  388. AskCoachCallback( true, this );
  389. }
  390. virtual void Decline()
  391. {
  392. AskCoachCallback( false, this );
  393. }
  394. static void AskCoachCallback( bool bConfirmed, void *pContext )
  395. {
  396. CTFAskCoachNotification *pNotification = (CTFAskCoachNotification*)pContext;
  397. GCSDK::CProtoBufMsg< CMsgTFCoaching_AskCoachResponse > msg( k_EMsgGCCoaching_AskCoachResponse );
  398. msg.Body().set_accept_coaching_assignment( bConfirmed );
  399. GCClientSystem()->BSendMessage( msg );
  400. if ( bConfirmed )
  401. {
  402. ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_JoiningStudent", true, false, 30.0f );
  403. }
  404. pNotification->MarkForDeletion();
  405. }
  406. protected:
  407. bool m_bStudentIsFriend;
  408. };
  409. //-----------------------------------------------------------------------------
  410. class CGCCoaching_AskCoach : public GCSDK::CGCClientJob
  411. {
  412. public:
  413. CGCCoaching_AskCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  414. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  415. {
  416. GCSDK::CProtoBufMsg< CMsgTFCoaching_AskCoach > msg( pNetPacket );
  417. if ( steamapicontext && steamapicontext->SteamUtils() )
  418. {
  419. bool bStudentIsFriend = msg.Body().has_student_is_friend() && msg.Body().student_is_friend();
  420. CTFAskCoachNotification *pNotification = new CTFAskCoachNotification( bStudentIsFriend );
  421. if ( bStudentIsFriend )
  422. {
  423. wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
  424. g_pVGuiLocalize->ConvertANSIToUnicode( InventoryManager()->PersonaName_Get( msg.Body().account_id_student() ), wszPlayerName, sizeof( wszPlayerName ) );
  425. pNotification->AddStringToken( "friend", wszPlayerName );
  426. }
  427. CSteamID steamID( msg.Body().account_id_student(), 1, GetUniverse(), k_EAccountTypeIndividual );
  428. pNotification->SetSteamID( steamID );
  429. NotificationQueue_Add( pNotification );
  430. }
  431. return true;
  432. }
  433. };
  434. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_AskCoach, "CGCCoaching_AskCoach", k_EMsgGCCoaching_AskCoach, GCSDK::k_EServerTypeGCClient );
  435. //-----------------------------------------------------------------------------
  436. class CGCCoaching_CoachJoinGame : public GCSDK::CGCClientJob
  437. {
  438. public:
  439. CGCCoaching_CoachJoinGame( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  440. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  441. {
  442. GCSDK::CProtoBufMsg< CMsgTFCoaching_CoachJoinGame > msg( pNetPacket );
  443. CloseWaitingDialog();
  444. if ( msg.Body().join_game() )
  445. {
  446. if ( msg.Body().has_server_address() && msg.Body().has_server_port() )
  447. {
  448. // join the game
  449. uint32 iAddress = msg.Body().server_address();
  450. uint16 iPort = msg.Body().server_port();
  451. char command[256];
  452. Q_snprintf( command, sizeof(command), "connect %d.%d.%d.%d:%d coaching\n", (iAddress >> 24) & 0xff, (iAddress >> 16) & 0xff, (iAddress >> 8) & 0xff, iAddress & 0xff, iPort);
  453. engine->ClientCmd_Unrestricted( command );
  454. }
  455. }
  456. else
  457. {
  458. ShowMessageBox( "#TF_Coach_JoinFail_Title", "#TF_Coach_JoinFail_Text", "#GameUI_OK" );
  459. }
  460. return true;
  461. }
  462. };
  463. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_CoachJoinGame, "CGCCoaching_CoachJoinGame", k_EMsgGCCoaching_CoachJoinGame, GCSDK::k_EServerTypeGCClient );
  464. class CGCCoaching_AlreadyRatedCoach : public GCSDK::CGCClientJob
  465. {
  466. public:
  467. CGCCoaching_AlreadyRatedCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  468. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  469. {
  470. ShowMessageBox( "#TF_Coach_AlreadyRatedCoach_Title", "#TF_Coach_AlreadyRatedCoach_Text", "#GameUI_OK" );
  471. return true;
  472. }
  473. };
  474. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_AlreadyRatedCoach, "CGCCoaching_AlreadyRatedCoach", k_EMsgGCCoaching_AlreadyRatedCoach, GCSDK::k_EServerTypeGCClient );
  475. //-----------------------------------------------------------------------------
  476. static void LikeCoachCallback( bool bConfirmed, void *pContext )
  477. {
  478. GCSDK::CProtoBufMsg< CMsgTFCoaching_LikeCurrentCoach > msg( k_EMsgGCCoaching_LikeCurrentCoach );
  479. msg.Body().set_like_coach( bConfirmed );
  480. GCClientSystem()->BSendMessage( msg );
  481. g_bCanLikeCoach = false;
  482. }
  483. static void PromptIfLikeCoach()
  484. {
  485. if ( g_bCanLikeCoach )
  486. {
  487. ShowConfirmDialog( "#TF_Coach_LikeCoach_Title", "#TF_Coach_LikeCoach_Text",
  488. "#TF_Coach_Yes", "#TF_Coach_No",
  489. &LikeCoachCallback );
  490. }
  491. }
  492. void CL_Coaching_LevelShutdown()
  493. {
  494. if ( g_pSelectPlayerForCoachDialog )
  495. {
  496. g_pSelectPlayerForCoachDialog->OnCommand( "cancel" );
  497. }
  498. bool bHadCoach = g_bHadCoach;
  499. g_bHadCoach = false;
  500. if ( bHadCoach )
  501. {
  502. PromptIfLikeCoach();
  503. }
  504. }
  505. //-----------------------------------------------------------------------------
  506. ConVar tf_coach_min_time_played( "tf_coach_min_time_played", "7200", FCVAR_CLIENTDLL | FCVAR_HIDDEN );
  507. ConVar tf_coach_request_nevershowagain( "tf_coach_request_nevershowagain", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN );
  508. // @return true if the current player has played less than a certain threshold of hours and
  509. // so is deemed eligible for coaching
  510. static bool Coaching_ShouldRequestCoach()
  511. {
  512. // cannot request a coach while in training
  513. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  514. {
  515. return false;
  516. }
  517. // cannot request a coach if server is full
  518. if ( IsServerFull() )
  519. {
  520. return false;
  521. }
  522. // Grab generic stats and add time played to total time played
  523. int totalTimePlayed = 0;
  524. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
  525. {
  526. ClassStats_t &classStats = CTFStatPanel::GetClassStats( iClass );
  527. totalTimePlayed += classStats.accumulated.m_iStat[TFSTAT_PLAYTIME] + classStats.accumulatedMVM.m_iStat[TFSTAT_PLAYTIME];
  528. }
  529. // Better to not risk losing slots needed for bot opponents to coaches, as this could
  530. // inadvertently reduce difficulty and/or induce undefined behaviors/conditions
  531. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  532. {
  533. return false;
  534. }
  535. // check how many hours this player has played
  536. const int minTimePlayedForCoachingEligibility = tf_coach_min_time_played.GetInt();
  537. return ( totalTimePlayed < minTimePlayedForCoachingEligibility );
  538. }
  539. // If the player is eligible for coaching, then prompt them
  540. void Coaching_CheckIfEligibleForCoaching()
  541. {
  542. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  543. if ( pLocalPlayer == NULL )
  544. {
  545. return;
  546. }
  547. // first time only...better way to do this?
  548. if ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED )
  549. {
  550. return;
  551. }
  552. if ( !tf_coach_request_nevershowagain.GetBool() && Coaching_ShouldRequestCoach() )
  553. {
  554. ShowConfirmOptOutDialog( "#TF_Coach_AskStudent_Title", "#TF_Coach_AskStudent_Text",
  555. "#TF_Coach_Yes", "#TF_Coach_No",
  556. "#TF_Coach_AskStudent_DoNotShowAgain", "tf_coach_request_nevershowagain",
  557. &FindCoach );
  558. }
  559. }
  560. //-----------------------------------------------------------------------------
  561. class CCoachedByPanel : public CHudElement, public vgui::EditablePanel
  562. {
  563. DECLARE_CLASS_SIMPLE( CCoachedByPanel, vgui::EditablePanel );
  564. public:
  565. CCoachedByPanel( const char *pElementName )
  566. : CHudElement( pElementName )
  567. , BaseClass( NULL, "CoachedByPanel" )
  568. , m_bCanLikeCoach( false )
  569. {
  570. vgui::Panel *pParent = g_pClientMode->GetViewport();
  571. SetParent( pParent );
  572. SetHiddenBits( HIDEHUD_MISCSTATUS );
  573. ListenForGameEvent( "localplayer_changeteam" );
  574. ListenForGameEvent( "player_changename" );
  575. }
  576. virtual ~CCoachedByPanel()
  577. {
  578. }
  579. virtual bool ShouldDraw( void )
  580. {
  581. if ( !CHudElement::ShouldDraw() )
  582. {
  583. return false;
  584. }
  585. C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  586. if ( pLocalTFPlayer == NULL || pLocalTFPlayer->m_hCoach == NULL || pLocalTFPlayer->GetObserverMode() > OBS_MODE_NONE )
  587. {
  588. return false;
  589. }
  590. if ( !IsVisible() )
  591. {
  592. g_bHadCoach = true;
  593. m_bCanLikeCoach = g_bCanLikeCoach;
  594. UpdateUI();
  595. InvalidateLayout();
  596. }
  597. if ( m_bCanLikeCoach != g_bCanLikeCoach )
  598. {
  599. m_bCanLikeCoach = g_bCanLikeCoach;
  600. UpdateUI();
  601. }
  602. return true;
  603. }
  604. virtual void PerformLayout( void )
  605. {
  606. int iXIndent = XRES(5);
  607. int iXPostdent = XRES(10);
  608. int iWidth = iXIndent + iXPostdent;
  609. int iTextW, iTextH;
  610. C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  611. if ( m_pCoachNameLabel && pLocalTFPlayer)
  612. {
  613. m_pCoachNameLabel->GetContentSize( iTextW, iTextH );
  614. iWidth += MAX( iTextW, m_minCoachNameLabelWidth );
  615. m_pCoachNameLabel->SetWide( iTextW );
  616. if ( m_pAvatar )
  617. {
  618. iWidth += m_pAvatar->GetWide();
  619. }
  620. SetSize( iWidth, GetTall() );
  621. if ( m_pBGPanel_Blue )
  622. {
  623. m_pBGPanel_Blue->SetSize( iWidth, GetTall() );
  624. }
  625. if ( m_pBGPanel_Red )
  626. {
  627. m_pBGPanel_Red->SetSize( iWidth, GetTall() );
  628. }
  629. if ( m_pBGPanel_Blue && m_pBGPanel_Red )
  630. {
  631. bool bRed = ( pLocalTFPlayer->GetTeamNumber() == TF_TEAM_RED );
  632. m_pBGPanel_Blue->SetVisible( !bRed );
  633. m_pBGPanel_Red->SetVisible( bRed );
  634. }
  635. }
  636. }
  637. virtual void ApplySchemeSettings( vgui::IScheme *scheme )
  638. {
  639. LoadControlSettings( "resource/UI/CoachedByPanel.res" );
  640. BaseClass::ApplySchemeSettings( scheme );
  641. m_pCoachNameLabel = dynamic_cast< vgui::Label* >( FindChildByName("CoachNameLabel") );
  642. m_minCoachNameLabelWidth = 0;
  643. if ( m_pCoachNameLabel )
  644. {
  645. m_minCoachNameLabelWidth = m_pCoachNameLabel->GetWide();
  646. }
  647. m_pBGPanel_Blue = FindChildByName("Background_Blue");
  648. m_pBGPanel_Red = FindChildByName("Background_Red");
  649. if ( m_pBGPanel_Blue )
  650. {
  651. m_pBGPanel_Blue->SetVisible( true );
  652. }
  653. m_pAvatar = dynamic_cast<CAvatarImagePanel *>( FindChildByName("AvatarImage") );
  654. UpdateUI();
  655. }
  656. virtual void FireGameEvent( IGameEvent *event )
  657. {
  658. C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  659. if ( pLocalTFPlayer == NULL || pLocalTFPlayer->m_hCoach == NULL )
  660. {
  661. return;
  662. }
  663. const char *name = event->GetName();
  664. if ( FStrEq( name, "localplayer_changeteam" ) ||
  665. ( FStrEq( name, "player_changename" ) && event->GetInt( "userid" ) == pLocalTFPlayer->m_hCoach->GetUserID() ) )
  666. {
  667. UpdateUI();
  668. InvalidateLayout();
  669. }
  670. }
  671. int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
  672. {
  673. if ( !IsVisible() )
  674. return 1; // key not handled
  675. if ( !down )
  676. return 1; // key not handled
  677. C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  678. if ( pLocalTFPlayer != NULL && pLocalTFPlayer->m_hCoach != NULL )
  679. {
  680. switch ( keynum )
  681. {
  682. case KEY_F7:
  683. {
  684. PromptIfLikeCoach();
  685. }
  686. break;
  687. case KEY_F8:
  688. {
  689. GCSDK::CProtoBufMsg< CMsgTFCoaching_RemoveCurrentCoach > msg( k_EMsgGCCoaching_RemoveCurrentCoach );
  690. GCClientSystem()->BSendMessage( msg );
  691. return 0;
  692. }
  693. break;
  694. }
  695. }
  696. return 1; // key not handled
  697. }
  698. void UpdateUI()
  699. {
  700. C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  701. if ( pLocalTFPlayer == NULL || m_pCoachNameLabel == NULL || pLocalTFPlayer->m_hCoach == NULL )
  702. {
  703. return;
  704. }
  705. C_TFPlayer *pCoachPlayer = pLocalTFPlayer->m_hCoach;
  706. const char* pCoachName = pCoachPlayer->GetPlayerName();
  707. wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
  708. g_pVGuiLocalize->ConvertANSIToUnicode(pCoachName, wszPlayerName, sizeof( wszPlayerName ) );
  709. wchar_t wszText[ 256 ] = L"";
  710. g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( "#TF_Coach_Coach_Prefix" ), 1, wszPlayerName );
  711. m_pCoachNameLabel->SetText( wszText );
  712. if ( m_pAvatar )
  713. {
  714. m_pAvatar->SetShouldDrawFriendIcon( false );
  715. if ( steamapicontext && steamapicontext->SteamUser() )
  716. {
  717. CSteamID coachSteamID;
  718. if ( pCoachPlayer->GetSteamID( &coachSteamID ) )
  719. {
  720. m_pAvatar->SetPlayer( coachSteamID, k_EAvatarSize64x64 );
  721. }
  722. else
  723. {
  724. m_pAvatar->ClearAvatar();
  725. }
  726. }
  727. }
  728. SetChildPanelVisible( this, "LikeCoachLabel", m_bCanLikeCoach );
  729. }
  730. protected:
  731. CAvatarImagePanel *m_pAvatar;
  732. vgui::Label *m_pCoachNameLabel;
  733. vgui::Panel *m_pBGPanel_Blue;
  734. vgui::Panel *m_pBGPanel_Red;
  735. int m_minCoachNameLabelWidth;
  736. bool m_bCanLikeCoach;
  737. };
  738. DECLARE_HUDELEMENT( CCoachedByPanel );
  739. //-----------------------------------------------------------------------------
  740. // @return true if the coaching panel handled the input
  741. // false otherwise
  742. bool CoachingHandlesKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
  743. {
  744. CCoachedByPanel *pCoachingPanel = ( CCoachedByPanel * )GET_HUDELEMENT( CCoachedByPanel );
  745. if ( pCoachingPanel )
  746. {
  747. return pCoachingPanel->HudElementKeyInput( down, keynum, pszCurrentBinding ) == 0;
  748. }
  749. return false;
  750. }