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.

590 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "basetypes.h"
  9. #include "hud.h"
  10. #include <string.h>
  11. #include <stdio.h>
  12. #include "voice_status.h"
  13. #include "r_efx.h"
  14. #include <vgui_controls/TextImage.h>
  15. #include <vgui/MouseCode.h>
  16. #include "cdll_client_int.h"
  17. #include "hud_macros.h"
  18. #include "c_playerresource.h"
  19. #include "cliententitylist.h"
  20. #include "c_baseplayer.h"
  21. #include "materialsystem/imesh.h"
  22. #include "view.h"
  23. #include "convar.h"
  24. #include <vgui_controls/Controls.h>
  25. #include <vgui/IScheme.h>
  26. #include <vgui/ISurface.h>
  27. #include "vgui_bitmapimage.h"
  28. #include "materialsystem/imaterial.h"
  29. #include "tier0/dbg.h"
  30. #include "cdll_int.h"
  31. #include <vgui/IPanel.h>
  32. // memdbgon must be the last include file in a .cpp file!!!
  33. #include "tier0/memdbgon.h"
  34. using namespace vgui;
  35. extern int cam_thirdperson;
  36. #define VOICE_MODEL_INTERVAL 0.3
  37. #define SQUELCHOSCILLATE_PER_SECOND 2.0f
  38. ConVar voice_modenable( "voice_modenable", "1", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Enable/disable voice in this mod." );
  39. ConVar voice_clientdebug( "voice_clientdebug", "0" );
  40. // ---------------------------------------------------------------------- //
  41. // The voice manager for the client.
  42. // ---------------------------------------------------------------------- //
  43. static CVoiceStatus *g_VoiceStatus = NULL;
  44. CVoiceStatus* GetClientVoiceMgr()
  45. {
  46. if ( !g_VoiceStatus )
  47. {
  48. ClientVoiceMgr_Init();
  49. }
  50. return g_VoiceStatus;
  51. }
  52. void ClientVoiceMgr_Init()
  53. {
  54. if ( g_VoiceStatus )
  55. return;
  56. g_VoiceStatus = new CVoiceStatus();
  57. }
  58. void ClientVoiceMgr_Shutdown()
  59. {
  60. delete g_VoiceStatus;
  61. g_VoiceStatus = NULL;
  62. }
  63. // ---------------------------------------------------------------------- //
  64. // CVoiceStatus.
  65. // ---------------------------------------------------------------------- //
  66. static CVoiceStatus *g_pInternalVoiceStatus = NULL;
  67. void __MsgFunc_VoiceMask(bf_read &msg)
  68. {
  69. if(g_pInternalVoiceStatus)
  70. g_pInternalVoiceStatus->HandleVoiceMaskMsg(msg);
  71. }
  72. void __MsgFunc_RequestState(bf_read &msg)
  73. {
  74. if(g_pInternalVoiceStatus)
  75. g_pInternalVoiceStatus->HandleReqStateMsg(msg);
  76. }
  77. // ---------------------------------------------------------------------- //
  78. // CVoiceStatus.
  79. // ---------------------------------------------------------------------- //
  80. CVoiceStatus::CVoiceStatus()
  81. {
  82. m_nControlSize = 0;
  83. m_bBanMgrInitialized = false;
  84. m_LastUpdateServerState = 0;
  85. m_bTalking = m_bServerAcked = false;
  86. #ifdef VOICE_VOX_ENABLE
  87. m_bAboveThresholdTimer.Invalidate();
  88. #endif // VOICE_VOX_ENABLE
  89. m_bServerModEnable = -1;
  90. m_pHeadLabelMaterial = NULL;
  91. m_bHeadLabelsDisabled = false;
  92. }
  93. CVoiceStatus::~CVoiceStatus()
  94. {
  95. if ( m_pHeadLabelMaterial )
  96. {
  97. m_pHeadLabelMaterial->DecrementReferenceCount();
  98. }
  99. g_pInternalVoiceStatus = NULL;
  100. const char *pGameDir = engine->GetGameDirectory();
  101. if( pGameDir )
  102. {
  103. if(m_bBanMgrInitialized)
  104. {
  105. m_BanMgr.SaveState( pGameDir );
  106. }
  107. }
  108. }
  109. int CVoiceStatus::Init(
  110. IVoiceStatusHelper *pHelper,
  111. VPANEL pParentPanel)
  112. {
  113. const char *pGameDir = engine->GetGameDirectory();
  114. if( pGameDir )
  115. {
  116. m_BanMgr.Init( pGameDir );
  117. m_bBanMgrInitialized = true;
  118. }
  119. Assert(!g_pInternalVoiceStatus);
  120. g_pInternalVoiceStatus = this;
  121. m_pHeadLabelMaterial = materials->FindMaterial( "voice/icntlk_pl", TEXTURE_GROUP_VGUI );
  122. m_pHeadLabelMaterial->IncrementReferenceCount();
  123. m_bInSquelchMode = false;
  124. m_pHelper = pHelper;
  125. m_pParentPanel = pParentPanel;
  126. HOOK_MESSAGE(VoiceMask);
  127. HOOK_MESSAGE(RequestState);
  128. return 1;
  129. }
  130. BitmapImage* vgui_LoadMaterial( vgui::VPANEL pParent, const char *pFilename )
  131. {
  132. return new BitmapImage( pParent, pFilename );
  133. }
  134. void CVoiceStatus::VidInit()
  135. {
  136. }
  137. void CVoiceStatus::Frame(double frametime)
  138. {
  139. // check server banned players once per second
  140. if (gpGlobals->curtime - m_LastUpdateServerState > 1)
  141. {
  142. UpdateServerState(false);
  143. }
  144. }
  145. float g_flHeadOffset = 35;
  146. float g_flHeadIconSize = 8;
  147. void CVoiceStatus::SetHeadLabelOffset( float offset )
  148. {
  149. g_flHeadOffset = offset;
  150. }
  151. float CVoiceStatus::GetHeadLabelOffset( void ) const
  152. {
  153. return g_flHeadOffset;
  154. }
  155. void CVoiceStatus::DrawHeadLabels()
  156. {
  157. if ( m_bHeadLabelsDisabled )
  158. return;
  159. if ( GameRules() && ( GameRules()->ShouldDrawHeadLabels() == false ) )
  160. return;
  161. if( !m_pHeadLabelMaterial )
  162. return;
  163. CMatRenderContextPtr pRenderContext( materials );
  164. for(int i=0; i < VOICE_MAX_PLAYERS; i++)
  165. {
  166. if ( !m_VoicePlayers[i] )
  167. continue;
  168. IClientNetworkable *pClient = cl_entitylist->GetClientEntity( i+1 );
  169. // Don't show an icon if the player is not in our PVS.
  170. if ( !pClient || pClient->IsDormant() )
  171. continue;
  172. C_BasePlayer *pPlayer = dynamic_cast<C_BasePlayer*>(pClient);
  173. if( !pPlayer )
  174. continue;
  175. // Don't show an icon for dead or spectating players (ie: invisible entities).
  176. if( pPlayer->IsPlayerDead() )
  177. continue;
  178. // Place it 20 units above his head.
  179. Vector vOrigin = pPlayer->WorldSpaceCenter();
  180. vOrigin.z += g_flHeadOffset;
  181. // Align it so it never points up or down.
  182. Vector vUp( 0, 0, 1 );
  183. Vector vRight = CurrentViewRight();
  184. if ( fabs( vRight.z ) > 0.95 ) // don't draw it edge-on
  185. continue;
  186. vRight.z = 0;
  187. VectorNormalize( vRight );
  188. float flSize = g_flHeadIconSize;
  189. pRenderContext->Bind( pPlayer->GetHeadLabelMaterial() );
  190. IMesh *pMesh = pRenderContext->GetDynamicMesh();
  191. CMeshBuilder meshBuilder;
  192. meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
  193. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  194. meshBuilder.TexCoord2f( 0,0,0 );
  195. meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * flSize)).Base() );
  196. meshBuilder.AdvanceVertex();
  197. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  198. meshBuilder.TexCoord2f( 0,1,0 );
  199. meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * flSize)).Base() );
  200. meshBuilder.AdvanceVertex();
  201. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  202. meshBuilder.TexCoord2f( 0,1,1 );
  203. meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * -flSize)).Base() );
  204. meshBuilder.AdvanceVertex();
  205. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  206. meshBuilder.TexCoord2f( 0,0,1 );
  207. meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * -flSize)).Base() );
  208. meshBuilder.AdvanceVertex();
  209. meshBuilder.End();
  210. pMesh->Draw();
  211. }
  212. }
  213. void CVoiceStatus::UpdateSpeakerStatus(int entindex, bool bTalking)
  214. {
  215. if(!m_pParentPanel)
  216. return;
  217. if( voice_clientdebug.GetInt() )
  218. {
  219. Msg( "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking );
  220. }
  221. // Is it the local player talking?
  222. if( entindex == -1 )
  223. {
  224. m_bTalking = !!bTalking;
  225. if( bTalking )
  226. {
  227. // Enable voice for them automatically if they try to talk.
  228. engine->ClientCmd( "voice_modenable 1" );
  229. }
  230. }
  231. else if( entindex == -2 )
  232. {
  233. m_bServerAcked = !!bTalking;
  234. }
  235. #ifdef VOICE_VOX_ENABLE
  236. else if( entindex == -3 )
  237. {
  238. if ( bTalking )
  239. {
  240. const float AboveThresholdMinDuration = 0.5f;
  241. m_bAboveThresholdTimer.Start( AboveThresholdMinDuration );
  242. }
  243. }
  244. #endif // VOICE_VOX_ENABLE
  245. else if(entindex > 0 && entindex <= VOICE_MAX_PLAYERS)
  246. {
  247. int iClient = entindex - 1;
  248. if(iClient < 0)
  249. return;
  250. if(bTalking)
  251. {
  252. m_VoicePlayers[iClient] = true;
  253. m_VoiceEnabledPlayers[iClient] = true;
  254. }
  255. else
  256. {
  257. m_VoicePlayers[iClient] = false;
  258. }
  259. }
  260. }
  261. void CVoiceStatus::UpdateServerState(bool bForce)
  262. {
  263. // Can't do anything when we're not in a level.
  264. if( !g_bLevelInitialized )
  265. {
  266. if( voice_clientdebug.GetInt() )
  267. {
  268. Msg( "CVoiceStatus::UpdateServerState: g_bLevelInitialized\n" );
  269. }
  270. return;
  271. }
  272. int bCVarModEnable = !!voice_modenable.GetInt();
  273. if(bForce || m_bServerModEnable != bCVarModEnable)
  274. {
  275. m_bServerModEnable = bCVarModEnable;
  276. char str[256];
  277. Q_snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable);
  278. engine->ServerCmd(str);
  279. if( voice_clientdebug.GetInt() )
  280. {
  281. Msg( "CVoiceStatus::UpdateServerState: Sending '%s'\n", str );
  282. }
  283. }
  284. char str[2048];
  285. Q_strncpy(str,"vban",sizeof(str));
  286. bool bChange = false;
  287. for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++)
  288. {
  289. unsigned long serverBanMask = 0;
  290. unsigned long banMask = 0;
  291. for(unsigned long i=0; i < 32; i++)
  292. {
  293. int playerIndex = ( dw * 32 + i );
  294. if ( playerIndex >= MAX_PLAYERS )
  295. break;
  296. player_info_t pi;
  297. if ( !engine->GetPlayerInfo( i+1, &pi ) )
  298. continue;
  299. if ( m_BanMgr.GetPlayerBan( pi.guid ) )
  300. {
  301. banMask |= 1 << i;
  302. }
  303. if ( m_ServerBannedPlayers[playerIndex] )
  304. {
  305. serverBanMask |= 1 << i;
  306. }
  307. }
  308. if ( serverBanMask != banMask )
  309. {
  310. bChange = true;
  311. }
  312. // Ok, the server needs to be updated.
  313. char numStr[512];
  314. Q_snprintf(numStr, sizeof(numStr), " %lx", banMask);
  315. Q_strncat(str, numStr, sizeof(str), COPY_ALL_CHARACTERS);
  316. }
  317. if(bChange || bForce)
  318. {
  319. if( voice_clientdebug.GetInt() )
  320. {
  321. Msg( "CVoiceStatus::UpdateServerState: Sending '%s'\n", str );
  322. }
  323. engine->ServerCmd( str, false ); // Tell the server..
  324. }
  325. else
  326. {
  327. if( voice_clientdebug.GetInt() )
  328. {
  329. Msg( "CVoiceStatus::UpdateServerState: no change\n" );
  330. }
  331. }
  332. m_LastUpdateServerState = gpGlobals->curtime;
  333. }
  334. void CVoiceStatus::HandleVoiceMaskMsg(bf_read &msg)
  335. {
  336. unsigned int dw;
  337. for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++)
  338. {
  339. m_AudiblePlayers.SetDWord(dw, (unsigned long)msg.ReadLong());
  340. m_ServerBannedPlayers.SetDWord(dw, (unsigned long)msg.ReadLong());
  341. if( voice_clientdebug.GetInt())
  342. {
  343. Msg("CVoiceStatus::HandleVoiceMaskMsg\n");
  344. Msg(" - m_AudiblePlayers[%d] = %u\n", dw, m_AudiblePlayers.GetDWord(dw));
  345. Msg(" - m_ServerBannedPlayers[%d] = %u\n", dw, m_ServerBannedPlayers.GetDWord(dw));
  346. }
  347. }
  348. m_bServerModEnable = msg.ReadByte();
  349. }
  350. void CVoiceStatus::HandleReqStateMsg(bf_read &msg)
  351. {
  352. if(voice_clientdebug.GetInt())
  353. {
  354. Msg("CVoiceStatus::HandleReqStateMsg\n");
  355. }
  356. UpdateServerState(true);
  357. }
  358. void CVoiceStatus::StartSquelchMode()
  359. {
  360. if(m_bInSquelchMode)
  361. return;
  362. m_bInSquelchMode = true;
  363. m_pHelper->UpdateCursorState();
  364. }
  365. void CVoiceStatus::StopSquelchMode()
  366. {
  367. m_bInSquelchMode = false;
  368. m_pHelper->UpdateCursorState();
  369. }
  370. bool CVoiceStatus::IsInSquelchMode()
  371. {
  372. return m_bInSquelchMode;
  373. }
  374. void SetOrUpdateBounds(
  375. vgui::Panel *pPanel,
  376. int left, int top, int wide, int tall,
  377. bool bOnlyUpdateBounds, int &topCoord, int &bottomCoord )
  378. {
  379. if ( bOnlyUpdateBounds )
  380. {
  381. if ( top < topCoord )
  382. topCoord = top;
  383. if ( (top+tall) >= bottomCoord )
  384. bottomCoord = top+tall;
  385. }
  386. else
  387. {
  388. pPanel->SetBounds( left, top, wide, tall );
  389. }
  390. }
  391. //-----------------------------------------------------------------------------
  392. // Purpose: returns true if the target client has been banned
  393. // Input : playerID -
  394. // Output : Returns true on success, false on failure.
  395. //-----------------------------------------------------------------------------
  396. bool CVoiceStatus::IsPlayerBlocked(int iPlayer)
  397. {
  398. player_info_t pi;
  399. if ( !engine->GetPlayerInfo( iPlayer, &pi ) )
  400. return false;
  401. return m_BanMgr.GetPlayerBan( pi.guid );
  402. }
  403. //-----------------------------------------------------------------------------
  404. // Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team)
  405. // Input : playerID -
  406. // Output : Returns true on success, false on failure.
  407. //-----------------------------------------------------------------------------
  408. bool CVoiceStatus::IsPlayerAudible(int iPlayer)
  409. {
  410. return !!m_AudiblePlayers[iPlayer-1];
  411. }
  412. //-----------------------------------------------------------------------------
  413. // returns true if the player is currently speaking
  414. //-----------------------------------------------------------------------------
  415. bool CVoiceStatus::IsPlayerSpeaking(int iPlayerIndex)
  416. {
  417. return m_VoicePlayers[iPlayerIndex-1] != 0;
  418. }
  419. //-----------------------------------------------------------------------------
  420. // returns true if the local player is attempting to speak
  421. //-----------------------------------------------------------------------------
  422. bool CVoiceStatus::IsLocalPlayerSpeaking( void )
  423. {
  424. #ifdef VOICE_VOX_ENABLE
  425. if ( voice_vox.GetBool() )
  426. {
  427. if ( m_bAboveThresholdTimer.IsElapsed() == true )
  428. {
  429. return false;
  430. }
  431. }
  432. #endif // VOICE_VOX_ENABLE
  433. return m_bTalking;
  434. }
  435. //-----------------------------------------------------------------------------
  436. // Purpose: blocks/unblocks the target client from being heard
  437. // Input : playerID -
  438. // Output : Returns true on success, false on failure.
  439. //-----------------------------------------------------------------------------
  440. void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked)
  441. {
  442. if (voice_clientdebug.GetInt())
  443. {
  444. Msg( "CVoiceStatus::SetPlayerBlockedState part 1\n" );
  445. }
  446. player_info_t pi;
  447. if ( !engine->GetPlayerInfo( iPlayer, &pi ) )
  448. return;
  449. if (voice_clientdebug.GetInt())
  450. {
  451. Msg( "CVoiceStatus::SetPlayerBlockedState part 2\n" );
  452. }
  453. // Squelch or (try to) unsquelch this player.
  454. if (voice_clientdebug.GetInt())
  455. {
  456. Msg("CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(pi.guid));
  457. }
  458. m_BanMgr.SetPlayerBan(pi.guid, !m_BanMgr.GetPlayerBan(pi.guid));
  459. UpdateServerState(false);
  460. }
  461. //-----------------------------------------------------------------------------
  462. // Purpose:
  463. //-----------------------------------------------------------------------------
  464. void CVoiceStatus::SetHeadLabelMaterial( const char *pszMaterial )
  465. {
  466. if ( m_pHeadLabelMaterial )
  467. {
  468. m_pHeadLabelMaterial->DecrementReferenceCount();
  469. m_pHeadLabelMaterial = NULL;
  470. }
  471. m_pHeadLabelMaterial = materials->FindMaterial( pszMaterial, TEXTURE_GROUP_VGUI );
  472. m_pHeadLabelMaterial->IncrementReferenceCount();
  473. }