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.

373 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "tf_gcmessages.h"
  8. #include "tf_item_inventory.h"
  9. #include "tf_player.h"
  10. #include "tf_duel_summary.h"
  11. #include "tf_gamerules.h"
  12. #include "gc_clientsystem.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. //-----------------------------------------------------------------------------
  16. struct duel_minigame_data_t
  17. {
  18. CSteamID m_steamIDInitiator;
  19. CSteamID m_steamIDTarget;
  20. uint16 m_usScoreInitiator;
  21. uint16 m_usScoreTarget;
  22. int m_iPlayerClass;
  23. bool operator==( const duel_minigame_data_t &rhs ) const
  24. {
  25. return m_steamIDInitiator == rhs.m_steamIDInitiator && m_steamIDTarget == rhs.m_steamIDTarget;
  26. }
  27. };
  28. CUtlVector< duel_minigame_data_t > g_duels;
  29. static duel_minigame_data_t *FindDuelBySteamID( const CSteamID &steamID )
  30. {
  31. FOR_EACH_VEC( g_duels, i )
  32. {
  33. duel_minigame_data_t &duel = g_duels[i];
  34. if ( duel.m_steamIDInitiator == steamID || duel.m_steamIDTarget == steamID )
  35. {
  36. return &duel;
  37. }
  38. }
  39. return NULL;
  40. }
  41. static void SpeakConceptBySteamID( const CSteamID &steamID, int iConcept, const CSteamID &steamIDInitiator, const CSteamID &steamIDTarget )
  42. {
  43. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerBySteamID( steamID ) );
  44. if ( pPlayer != NULL)
  45. {
  46. CTFPlayer *pPlayerInitiator = ToTFPlayer( GetPlayerBySteamID( steamIDInitiator ) );
  47. CTFPlayer *pPlayerTarget = ToTFPlayer( GetPlayerBySteamID( steamIDTarget ) );
  48. char pModifiers[256] = "";
  49. if ( pPlayerInitiator && pPlayerTarget )
  50. {
  51. Q_snprintf( pModifiers, sizeof(pModifiers), "duelinitiatorclass:%s,dueltargetclass:%s",
  52. g_aPlayerClassNames_NonLocalized[ pPlayerInitiator->m_Shared.InCond( TF_COND_DISGUISED ) ? pPlayerInitiator->m_Shared.GetDisguiseClass() : pPlayerInitiator->GetPlayerClass()->GetClassIndex() ],
  53. g_aPlayerClassNames_NonLocalized[ pPlayerTarget->m_Shared.InCond( TF_COND_DISGUISED ) ? pPlayerTarget->m_Shared.GetDisguiseClass() : pPlayerTarget->GetPlayerClass()->GetClassIndex() ]
  54. );
  55. }
  56. pPlayer->SpeakConceptIfAllowed( iConcept, pModifiers );
  57. }
  58. }
  59. static void RemoveDuel( duel_minigame_data_t *pDuel )
  60. {
  61. g_duels.FindAndFastRemove( *pDuel );
  62. }
  63. static void SendDuelResults( duel_minigame_data_t &duel, const CSteamID &steamIDWinner, eDuelEndReason eReason )
  64. {
  65. GCSDK::CGCMsg<MsgGC_Duel_Results_t> msg( k_EMsgGC_Duel_Results );
  66. msg.Body().m_ulInitiatorSteamID = duel.m_steamIDInitiator.ConvertToUint64();
  67. msg.Body().m_ulTargetSteamID = duel.m_steamIDTarget.ConvertToUint64();
  68. msg.Body().m_ulWinnerSteamID = steamIDWinner.ConvertToUint64();
  69. msg.Body().m_usScoreInitiator = duel.m_usScoreInitiator;
  70. msg.Body().m_usScoreTarget = duel.m_usScoreTarget;
  71. msg.Body().m_usEndReason = eReason;
  72. GCClientSystem()->BSendMessage( msg );
  73. }
  74. typedef enum
  75. {
  76. kDuelScoreType_Kill,
  77. kDuelScoreType_Assist,
  78. kMaxDuelScoreTypes,
  79. } eDuelScoreType;
  80. static int kDuelScoreTypes[kMaxDuelScoreTypes] = { 1, 1 };
  81. static void UpdateDuelScore( CTFPlayer *pKiller, CTFPlayer *pVictim, eDuelScoreType scoreType )
  82. {
  83. CSteamID steamIDKiller;
  84. CSteamID steamIDVictim;
  85. if ( pKiller->GetSteamID( &steamIDKiller ) == false || pVictim->GetSteamID( &steamIDVictim ) == false )
  86. return;
  87. int iScoreIncrement = kDuelScoreTypes[ scoreType ];
  88. FOR_EACH_VEC( g_duels, i )
  89. {
  90. duel_minigame_data_t &duel = g_duels[i];
  91. if ( ( duel.m_steamIDInitiator == steamIDKiller && duel.m_steamIDTarget == steamIDVictim ) ||
  92. ( duel.m_steamIDInitiator == steamIDVictim && duel.m_steamIDTarget == steamIDKiller ) )
  93. {
  94. // if we have a class restriction...
  95. bool bCountScore = true;
  96. if ( duel.m_iPlayerClass >= TF_FIRST_NORMAL_CLASS && duel.m_iPlayerClass < TF_LAST_NORMAL_CLASS )
  97. {
  98. bCountScore = ( pKiller->GetPlayerClass() != NULL && duel.m_iPlayerClass == pKiller->GetPlayerClass()->GetClassIndex() &&
  99. pVictim->GetPlayerClass() != NULL && duel.m_iPlayerClass == pVictim->GetPlayerClass()->GetClassIndex() );
  100. }
  101. if ( bCountScore )
  102. {
  103. // send appropriate event to all clients
  104. IGameEvent * event = gameeventmanager->CreateEvent( "duel_status" );
  105. if ( event )
  106. {
  107. event->SetInt( "killer", pKiller->GetUserID() );
  108. event->SetInt( "score_type", scoreType );
  109. if ( steamIDKiller == duel.m_steamIDInitiator )
  110. {
  111. event->SetInt( "initiator", pKiller->GetUserID() );
  112. event->SetInt( "target", pVictim->GetUserID() );
  113. duel.m_usScoreInitiator += iScoreIncrement;
  114. }
  115. else
  116. {
  117. event->SetInt( "initiator", pVictim->GetUserID() );
  118. event->SetInt( "target", pKiller->GetUserID() );
  119. duel.m_usScoreTarget += iScoreIncrement;
  120. }
  121. event->SetInt( "initiator_score", duel.m_usScoreInitiator );
  122. event->SetInt( "target_score", duel.m_usScoreTarget );
  123. gameeventmanager->FireEvent( event );
  124. }
  125. }
  126. break;
  127. }
  128. }
  129. }
  130. /**
  131. * Duel request
  132. */
  133. class CGC_GameServer_Duel_Request : public GCSDK::CGCClientJob
  134. {
  135. public:
  136. CGC_GameServer_Duel_Request( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  137. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  138. {
  139. GCSDK::CGCMsg<MsgGC_Duel_Request_t> msg( pNetPacket );
  140. SpeakConceptBySteamID( msg.Body().m_ulInitiatorSteamID, MP_CONCEPT_DUEL_REQUEST, msg.Body().m_ulInitiatorSteamID, msg.Body().m_ulTargetSteamID );
  141. return true;
  142. }
  143. };
  144. GC_REG_JOB( GCSDK::CGCClient, CGC_GameServer_Duel_Request, "CGC_GameServer_Duel_Request", k_EMsgGC_Duel_Request, GCSDK::k_EServerTypeGCClient );
  145. /**
  146. * Duel response
  147. */
  148. class CGC_GameServer_Duel_Response : public GCSDK::CGCClientJob
  149. {
  150. public:
  151. CGC_GameServer_Duel_Response( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  152. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  153. {
  154. GCSDK::CGCMsg<MsgGC_Duel_Response_t> msg( pNetPacket );
  155. // make sure we're still allowed to start a duel because the
  156. // game state may have changed since the duel request was sent
  157. if ( TFGameRules() && !TFGameRules()->CanInitiateDuels() )
  158. {
  159. // if they accepted the duel somehow, we need to cancel it with the GC
  160. if ( msg.Body().m_bAccepted )
  161. {
  162. GCSDK::CGCMsg<MsgGC_Duel_Results_t> msgDuelResults( k_EMsgGC_Duel_Results );
  163. msgDuelResults.Body().m_ulInitiatorSteamID = msg.Body().m_ulInitiatorSteamID;
  164. msgDuelResults.Body().m_ulTargetSteamID = msg.Body().m_ulTargetSteamID;
  165. msgDuelResults.Body().m_ulWinnerSteamID = 0;
  166. msgDuelResults.Body().m_usScoreInitiator = 0;
  167. msgDuelResults.Body().m_usScoreTarget = 0;
  168. msgDuelResults.Body().m_usEndReason = kDuelEndReason_Cancelled;
  169. GCClientSystem()->BSendMessage( msgDuelResults );
  170. }
  171. return true;
  172. }
  173. // duel was rejected
  174. if ( msg.Body().m_bAccepted == false )
  175. {
  176. SpeakConceptBySteamID( msg.Body().m_ulInitiatorSteamID, MP_CONCEPT_DUEL_REJECTED, msg.Body().m_ulInitiatorSteamID, msg.Body().m_ulTargetSteamID );
  177. SpeakConceptBySteamID( msg.Body().m_ulTargetSteamID, MP_CONCEPT_DUEL_TARGET_REJECT, msg.Body().m_ulInitiatorSteamID, msg.Body().m_ulTargetSteamID );
  178. return true;
  179. }
  180. // duel was accepted
  181. SpeakConceptBySteamID( msg.Body().m_ulInitiatorSteamID, MP_CONCEPT_DUEL_ACCEPTED, msg.Body().m_ulInitiatorSteamID, msg.Body().m_ulTargetSteamID );
  182. SpeakConceptBySteamID( msg.Body().m_ulTargetSteamID, MP_CONCEPT_DUEL_TARGET_ACCEPT, msg.Body().m_ulInitiatorSteamID, msg.Body().m_ulTargetSteamID );
  183. int idx = g_duels.AddToTail();
  184. duel_minigame_data_t &duel = g_duels[idx];
  185. memset( &duel, 0, sizeof(duel) );
  186. duel.m_steamIDInitiator = msg.Body().m_ulInitiatorSteamID;
  187. duel.m_steamIDTarget = msg.Body().m_ulTargetSteamID;
  188. duel.m_iPlayerClass = msg.Body().m_usAsPlayerClass;
  189. if ( duel.m_iPlayerClass >= TF_FIRST_NORMAL_CLASS && duel.m_iPlayerClass < TF_LAST_NORMAL_CLASS )
  190. {
  191. CTFPlayer *pPlayer_Initiator = ToTFPlayer( GetPlayerBySteamID( msg.Body().m_ulInitiatorSteamID ) );
  192. CTFPlayer *pPlayer_Target = ToTFPlayer( GetPlayerBySteamID( msg.Body().m_ulTargetSteamID ) );
  193. if ( pPlayer_Initiator && ( pPlayer_Initiator->GetPlayerClass() == NULL || pPlayer_Initiator->GetPlayerClass()->GetClassIndex() != duel.m_iPlayerClass ) )
  194. {
  195. pPlayer_Initiator->SetDesiredPlayerClassIndex( duel.m_iPlayerClass );
  196. pPlayer_Initiator->ForceRespawn();
  197. }
  198. if ( pPlayer_Target && ( pPlayer_Target->GetPlayerClass() == NULL || pPlayer_Target->GetPlayerClass()->GetClassIndex() != duel.m_iPlayerClass ) )
  199. {
  200. pPlayer_Target->SetDesiredPlayerClassIndex( duel.m_iPlayerClass );
  201. pPlayer_Target->ForceRespawn();
  202. }
  203. }
  204. return true;
  205. }
  206. };
  207. GC_REG_JOB( GCSDK::CGCClient, CGC_GameServer_Duel_Response, "CGC_GameServer_Duel_Response", k_EMsgGC_Duel_Response, GCSDK::k_EServerTypeGCClient );
  208. bool DuelMiniGame_IsInDuel( CTFPlayer *pPlayer )
  209. {
  210. CSteamID steamID;
  211. if ( pPlayer->GetSteamID( &steamID ) == false )
  212. {
  213. return false;
  214. }
  215. duel_minigame_data_t *pDuel = FindDuelBySteamID( steamID );
  216. return pDuel != NULL;
  217. }
  218. int DuelMiniGame_GetRequiredPlayerClass( CTFPlayer *pPlayer )
  219. {
  220. CSteamID steamID;
  221. if ( pPlayer->GetSteamID( &steamID ) == false )
  222. {
  223. return false;
  224. }
  225. duel_minigame_data_t *pDuel = FindDuelBySteamID( steamID );
  226. return pDuel != NULL ? pDuel->m_iPlayerClass : TF_CLASS_UNDEFINED;
  227. }
  228. void DuelMiniGame_NotifyKill( CTFPlayer *pKiller, CTFPlayer *pVictim )
  229. {
  230. UpdateDuelScore( pKiller, pVictim, kDuelScoreType_Kill );
  231. }
  232. void DuelMiniGame_NotifyAssist( CTFPlayer *pAssister, CTFPlayer *pVictim )
  233. {
  234. UpdateDuelScore( pAssister, pVictim, kDuelScoreType_Assist );
  235. }
  236. void DuelMiniGame_NotifyPlayerChangedTeam( CTFPlayer *pPlayer, int iNewTeam, bool bInitiatedByPlayer )
  237. {
  238. CSteamID steamIDPlayerWhoChangedTeams;
  239. if ( pPlayer->GetSteamID( &steamIDPlayerWhoChangedTeams ) == false )
  240. {
  241. return;
  242. }
  243. duel_minigame_data_t *pDuel = FindDuelBySteamID( steamIDPlayerWhoChangedTeams );
  244. if ( pDuel == NULL )
  245. {
  246. return;
  247. }
  248. CSteamID steamIDOpponent;
  249. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  250. {
  251. CTFPlayer *pOpponent = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  252. if ( pOpponent == NULL || pOpponent == pPlayer )
  253. continue;
  254. if ( pOpponent->GetSteamID( &steamIDOpponent ) == false )
  255. continue;
  256. if ( steamIDOpponent == pDuel->m_steamIDInitiator || steamIDOpponent == pDuel->m_steamIDTarget )
  257. {
  258. // player is disconnecting?
  259. if ( iNewTeam == TEAM_UNASSIGNED )
  260. {
  261. SendDuelResults( *pDuel, steamIDOpponent, kDuelEndReason_PlayerDisconnected );
  262. RemoveDuel( pDuel );
  263. }
  264. // found the opponent, if they are on the same team or the player is on spectator...
  265. else if ( iNewTeam == TEAM_SPECTATOR ||
  266. iNewTeam == pOpponent->GetTeamNumber() )
  267. {
  268. SendDuelResults( *pDuel, steamIDOpponent, bInitiatedByPlayer ? kDuelEndReason_PlayerSwappedTeams : kDuelEndReason_PlayerForceSwappedTeams );
  269. RemoveDuel( pDuel );
  270. }
  271. return;
  272. }
  273. }
  274. }
  275. void DuelMiniGame_NotifyPlayerDisconnect( CTFPlayer *pPlayer, bool bKicked )
  276. {
  277. CSteamID steamIDPlayerWhoChangedTeams;
  278. if ( pPlayer->GetSteamID( &steamIDPlayerWhoChangedTeams ) == false )
  279. {
  280. return;
  281. }
  282. duel_minigame_data_t *pDuel = FindDuelBySteamID( steamIDPlayerWhoChangedTeams );
  283. if ( pDuel == NULL )
  284. {
  285. return;
  286. }
  287. CSteamID steamIDOpponent;
  288. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  289. {
  290. CTFPlayer *pOpponent = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  291. if ( pOpponent == NULL || pOpponent == pPlayer )
  292. continue;
  293. if ( pOpponent->GetSteamID( &steamIDOpponent ) == false )
  294. continue;
  295. if ( steamIDOpponent == pDuel->m_steamIDInitiator || steamIDOpponent == pDuel->m_steamIDTarget )
  296. {
  297. SendDuelResults( *pDuel, steamIDOpponent, bKicked ? kDuelEndReason_PlayerKicked : kDuelEndReason_PlayerDisconnected );
  298. RemoveDuel( pDuel );
  299. return;
  300. }
  301. }
  302. }
  303. void DuelMiniGame_AssignWinners()
  304. {
  305. // send a message to the GC for each duel and remove the duels afterwards
  306. FOR_EACH_VEC( g_duels, i )
  307. {
  308. duel_minigame_data_t &duel = g_duels[i];
  309. if ( duel.m_usScoreInitiator == 0 && duel.m_usScoreTarget == 0 )
  310. {
  311. SendDuelResults( duel, duel.m_usScoreInitiator > duel.m_usScoreTarget ? duel.m_steamIDInitiator : duel.m_steamIDTarget, kDuelEndReason_ScoreTiedAtZero );
  312. }
  313. else if ( duel.m_usScoreInitiator == duel.m_usScoreTarget )
  314. {
  315. SendDuelResults( duel, duel.m_usScoreInitiator > duel.m_usScoreTarget ? duel.m_steamIDInitiator : duel.m_steamIDTarget, kDuelEndReason_ScoreTied );
  316. }
  317. else
  318. {
  319. SendDuelResults( duel, duel.m_usScoreInitiator > duel.m_usScoreTarget ? duel.m_steamIDInitiator : duel.m_steamIDTarget, kDuelEndReason_DuelOver );
  320. }
  321. }
  322. g_duels.RemoveAll();
  323. }
  324. void DuelMiniGame_Stop()
  325. {
  326. DuelMiniGame_AssignWinners();
  327. }
  328. void DuelMiniGame_LevelShutdown()
  329. {
  330. // send a message to the GC for each duel and remove the duels afterwards
  331. FOR_EACH_VEC( g_duels, i )
  332. {
  333. duel_minigame_data_t &duel = g_duels[i];
  334. SendDuelResults( duel, duel.m_usScoreInitiator > duel.m_usScoreTarget ? duel.m_steamIDInitiator : duel.m_steamIDTarget, kDuelEndReason_LevelShutdown );
  335. }
  336. g_duels.RemoveAll();
  337. }