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.

421 lines
9.5 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include <string.h>
  9. #include <stdio.h>
  10. #include "voice_status.h"
  11. #include "radio_status.h"
  12. #include "c_playerresource.h"
  13. #include "cliententitylist.h"
  14. #include "c_baseplayer.h"
  15. #include "materialsystem/imesh.h"
  16. #include "view.h"
  17. #include "materialsystem/imaterial.h"
  18. #include "tier0/dbg.h"
  19. #include "cdll_int.h"
  20. #include "c_cs_player.h"
  21. #include "menu.h" // for CHudMenu defs
  22. // memdbgon must be the last include file in a .cpp file!!!
  23. #include "tier0/memdbgon.h"
  24. using namespace vgui;
  25. // ---------------------------------------------------------------------- //
  26. // The radio feedback manager for the client.
  27. // ---------------------------------------------------------------------- //
  28. static CRadioStatus s_RadioStatus;
  29. //
  30. //-----------------------------------------------------
  31. //
  32. // Stuff for the Radio Menus
  33. static void radio1_f( void );
  34. static void radio2_f( void );
  35. static void radio3_f( void );
  36. static ConCommand radio1( "radio1", radio1_f, "Opens a radio menu" );
  37. static ConCommand radio2( "radio2", radio2_f, "Opens a radio menu" );
  38. static ConCommand radio3( "radio3", radio3_f, "Opens a radio menu" );
  39. static int g_whichMenu = 0;
  40. //
  41. //--------------------------------------------------------------
  42. //
  43. // These methods will bring up the radio menus from the client side.
  44. // They mimic the old server commands of the same name, which used
  45. // to require a round-trip causing latency and unreliability in
  46. // menu responses. Only 1 message is sent to the server now which
  47. // includes both the menu name and the selected item. The server
  48. // is never informed that the menu has been displayed.
  49. //
  50. //--------------------------------------------------------------
  51. //
  52. void OpenRadioMenu( int index )
  53. {
  54. // do not show the menu if the player is dead or is an observer
  55. C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
  56. if ( !pPlayer )
  57. return;
  58. if ( !pPlayer->IsAlive() || pPlayer->IsObserver() )
  59. return;
  60. CHudMenu *pMenu = (CHudMenu *) gHUD.FindElement( "CHudMenu" );
  61. if ( !pMenu )
  62. return;
  63. g_whichMenu = index;
  64. //
  65. // the 0x23f and 0x3ff are masks that describes the keys that
  66. // are valid. This will have to be changed if the menus change.
  67. //
  68. switch ( index )
  69. {
  70. case 1:
  71. pMenu->ShowMenu( "#RadioA", 0x23f );
  72. break;
  73. case 2:
  74. pMenu->ShowMenu( "#RadioB", 0x23f );
  75. break;
  76. case 3:
  77. pMenu->ShowMenu( "#RadioC", 0x3ff );
  78. break;
  79. default:
  80. g_whichMenu = 0;
  81. }
  82. }
  83. static void radio1_f( void )
  84. {
  85. OpenRadioMenu( 1 );
  86. }
  87. static void radio2_f( void )
  88. {
  89. OpenRadioMenu( 2 );
  90. }
  91. static void radio3_f( void )
  92. {
  93. OpenRadioMenu( 3 );
  94. }
  95. CON_COMMAND_F( menuselect, "menuselect", FCVAR_CLIENTCMD_CAN_EXECUTE )
  96. {
  97. if ( args.ArgC() < 2 )
  98. return;
  99. if( g_whichMenu == 0 )
  100. {
  101. // if we didn't have a menu open, maybe a plugin did. send it on to the server.
  102. const char *cmd = VarArgs( "menuselect %s", args[1] );
  103. engine->ServerCmd( cmd );
  104. return;
  105. }
  106. int whichEntry = atoi( args[ 1 ] );
  107. switch( g_whichMenu )
  108. {
  109. case 1: //RadioA
  110. {
  111. switch( whichEntry )
  112. {
  113. case 1: // coverme
  114. engine->ClientCmd( "coverme" );
  115. break;
  116. case 2: // takepoint
  117. engine->ClientCmd( "takepoint" );
  118. break;
  119. case 3: // holdpos
  120. engine->ClientCmd( "holdpos" );
  121. break;
  122. case 4: // regroup
  123. engine->ClientCmd( "regroup" );
  124. break;
  125. case 5: // followme
  126. engine->ClientCmd( "followme" );
  127. break;
  128. case 6: // takingfire
  129. engine->ClientCmd( "takingfire" );
  130. break;
  131. }
  132. }
  133. break;
  134. case 2: //RadioB
  135. {
  136. switch( whichEntry )
  137. {
  138. case 1: // go
  139. engine->ClientCmd( "go" );
  140. break;
  141. case 2: // fallback
  142. engine->ClientCmd( "fallback" );
  143. break;
  144. case 3: // sticktog
  145. engine->ClientCmd( "sticktog" );
  146. break;
  147. case 4: // getinpos
  148. engine->ClientCmd( "getinpos" );
  149. break;
  150. case 5: // stormfront
  151. engine->ClientCmd( "stormfront" );
  152. break;
  153. case 6: // report
  154. engine->ClientCmd( "report" );
  155. break;
  156. }
  157. }
  158. break;
  159. case 3: //RadioC
  160. {
  161. switch( whichEntry )
  162. {
  163. case 1: // roger
  164. engine->ClientCmd( "roger" );
  165. break;
  166. case 2: // enemyspot
  167. engine->ClientCmd( "enemyspot" );
  168. break;
  169. case 3: // needbackup
  170. engine->ClientCmd( "needbackup" );
  171. break;
  172. case 4: // sectorclear
  173. engine->ClientCmd( "sectorclear" );
  174. break;
  175. case 5: // inposition
  176. engine->ClientCmd( "inposition" );
  177. break;
  178. case 6: // reportingin
  179. engine->ClientCmd( "reportingin" );
  180. break;
  181. case 7: // getout
  182. engine->ClientCmd( "getout" );
  183. break;
  184. case 8: // negative
  185. engine->ClientCmd( "negative" );
  186. break;
  187. case 9: // enemydown
  188. engine->ClientCmd( "enemydown" );
  189. break;
  190. }
  191. }
  192. break;
  193. default:
  194. // if we didn't have a menu open, maybe a plugin did. send it on to the server.
  195. const char *cmd = VarArgs( "menuselect %d", whichEntry );
  196. engine->ServerCmd( cmd );
  197. break;
  198. }
  199. // reset menu
  200. g_whichMenu = 0;
  201. }
  202. //
  203. //-----------------------------------------------------
  204. //
  205. CRadioStatus* RadioManager()
  206. {
  207. return &s_RadioStatus;
  208. }
  209. // ---------------------------------------------------------------------- //
  210. // CRadioStatus.
  211. // ---------------------------------------------------------------------- //
  212. CRadioStatus::CRadioStatus()
  213. {
  214. m_pHeadLabelMaterial = NULL;
  215. Q_memset(m_radioUntil, 0, sizeof(m_radioUntil));
  216. Q_memset(m_voiceUntil, 0, sizeof(m_voiceUntil));
  217. }
  218. bool CRadioStatus::Init()
  219. {
  220. if ( !m_pHeadLabelMaterial )
  221. {
  222. m_pHeadLabelMaterial = materials->FindMaterial( "sprites/radio", TEXTURE_GROUP_VGUI );
  223. }
  224. if ( IsErrorMaterial( m_pHeadLabelMaterial ) )
  225. return false;
  226. m_pHeadLabelMaterial->IncrementReferenceCount();
  227. return true;
  228. }
  229. void CRadioStatus::Shutdown()
  230. {
  231. if ( m_pHeadLabelMaterial )
  232. m_pHeadLabelMaterial->DecrementReferenceCount();
  233. m_pHeadLabelMaterial = NULL;
  234. }
  235. void CRadioStatus::LevelInitPostEntity()
  236. {
  237. ExpireBotVoice( true );
  238. Q_memset(m_radioUntil, 0, sizeof(m_radioUntil));
  239. Q_memset(m_voiceUntil, 0, sizeof(m_voiceUntil));
  240. }
  241. void CRadioStatus::LevelShutdownPreEntity()
  242. {
  243. ExpireBotVoice( true );
  244. Q_memset(m_radioUntil, 0, sizeof(m_radioUntil));
  245. Q_memset(m_voiceUntil, 0, sizeof(m_voiceUntil));
  246. }
  247. extern float g_flHeadIconSize;
  248. static float s_flHeadOffset = 3;
  249. static float s_flHeadIconSize = 7;
  250. void CRadioStatus::DrawHeadLabels()
  251. {
  252. ExpireBotVoice();
  253. if( !m_pHeadLabelMaterial )
  254. return;
  255. for(int i=0; i < VOICE_MAX_PLAYERS; i++)
  256. {
  257. if ( m_radioUntil[i] < gpGlobals->curtime )
  258. continue;
  259. IClientNetworkable *pClient = cl_entitylist->GetClientEntity( i+1 );
  260. // Don't show an icon if the player is not in our PVS.
  261. if ( !pClient || pClient->IsDormant() )
  262. continue;
  263. C_BasePlayer *pPlayer = dynamic_cast<C_BasePlayer*>(pClient);
  264. if( !pPlayer )
  265. continue;
  266. // Don't show an icon for dead or spectating players (ie: invisible entities).
  267. if( pPlayer->IsPlayerDead() )
  268. continue;
  269. // Place it above his head.
  270. Vector vOrigin = pPlayer->WorldSpaceCenter();
  271. vOrigin.z += GetClientVoiceMgr()->GetHeadLabelOffset() + s_flHeadOffset;
  272. if ( GetClientVoiceMgr()->IsPlayerSpeaking( i+1 ) )
  273. {
  274. vOrigin.z += g_flHeadIconSize;
  275. }
  276. // Align it so it never points up or down.
  277. Vector vUp( 0, 0, 1 );
  278. Vector vRight = CurrentViewRight();
  279. if ( fabs( vRight.z ) > 0.95 ) // don't draw it edge-on
  280. continue;
  281. vRight.z = 0;
  282. VectorNormalize( vRight );
  283. float flSize = s_flHeadIconSize;
  284. CMatRenderContextPtr pRenderContext( materials );
  285. pRenderContext->Bind( m_pHeadLabelMaterial );
  286. IMesh *pMesh = pRenderContext->GetDynamicMesh();
  287. CMeshBuilder meshBuilder;
  288. meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
  289. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  290. meshBuilder.TexCoord2f( 0,0,0 );
  291. meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * flSize)).Base() );
  292. meshBuilder.AdvanceVertex();
  293. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  294. meshBuilder.TexCoord2f( 0,1,0 );
  295. meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * flSize)).Base() );
  296. meshBuilder.AdvanceVertex();
  297. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  298. meshBuilder.TexCoord2f( 0,1,1 );
  299. meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * -flSize)).Base() );
  300. meshBuilder.AdvanceVertex();
  301. meshBuilder.Color3f( 1.0, 1.0, 1.0 );
  302. meshBuilder.TexCoord2f( 0,0,1 );
  303. meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * -flSize)).Base() );
  304. meshBuilder.AdvanceVertex();
  305. meshBuilder.End();
  306. pMesh->Draw();
  307. }
  308. }
  309. void CRadioStatus::UpdateRadioStatus(int entindex, float duration)
  310. {
  311. if(entindex > 0 && entindex <= VOICE_MAX_PLAYERS)
  312. {
  313. int iClient = entindex - 1;
  314. if(iClient < 0)
  315. return;
  316. m_radioUntil[iClient] = gpGlobals->curtime + duration;
  317. }
  318. }
  319. void CRadioStatus::UpdateVoiceStatus(int entindex, float duration)
  320. {
  321. if(entindex > 0 && entindex <= VOICE_MAX_PLAYERS)
  322. {
  323. int iClient = entindex - 1;
  324. if(iClient < 0)
  325. return;
  326. m_voiceUntil[iClient] = gpGlobals->curtime + duration;
  327. GetClientVoiceMgr()->UpdateSpeakerStatus( entindex, true );
  328. }
  329. }
  330. void CRadioStatus::ExpireBotVoice( bool force )
  331. {
  332. for(int i=0; i < VOICE_MAX_PLAYERS; i++)
  333. {
  334. if ( m_voiceUntil[i] > 0.0f )
  335. {
  336. bool expire = force;
  337. C_CSPlayer *player = static_cast<C_CSPlayer*>( cl_entitylist->GetEnt(i+1) );
  338. if ( !player )
  339. {
  340. // player left the game
  341. expire = true;
  342. }
  343. else if ( m_voiceUntil[i] < gpGlobals->curtime )
  344. {
  345. // player is done speaking
  346. expire = true;
  347. }
  348. if ( expire )
  349. {
  350. m_voiceUntil[i] = 0.0f;
  351. GetClientVoiceMgr()->UpdateSpeakerStatus( i+1, false );
  352. }
  353. }
  354. }
  355. }