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.

5595 lines
148 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: The TF Game rules
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "dod_gamerules.h"
  9. #include "ammodef.h"
  10. #include "KeyValues.h"
  11. #include "weapon_dodbase.h"
  12. #include "filesystem.h" // for WriteStatsFile
  13. #ifdef CLIENT_DLL
  14. #include "precache_register.h"
  15. #include "c_dod_player.h"
  16. #else
  17. #include "coordsize.h"
  18. #include "dod_player.h"
  19. #include "voice_gamemgr.h"
  20. #include "team.h"
  21. #include "dod_player.h"
  22. #include "dod_bot_temp.h"
  23. #include "game.h"
  24. #include "dod_shareddefs.h"
  25. #include "player_resource.h"
  26. #include "mapentities.h"
  27. #include "dod_gameinterface.h"
  28. #include "dod_objective_resource.h"
  29. #include "dod_cvars.h"
  30. #include "dod_team.h"
  31. #include "dod_playerclass_info_parse.h"
  32. #include "dod_control_point_master.h"
  33. #include "dod_bombtarget.h"
  34. //#include "teamplayroundbased_gamerules.h"
  35. #include "weapon_dodbipodgun.h"
  36. #endif
  37. // memdbgon must be the last include file in a .cpp file!!!
  38. #include "tier0/memdbgon.h"
  39. #ifndef CLIENT_DLL
  40. BEGIN_DATADESC(CSpawnPoint)
  41. // Keyfields
  42. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  43. // Inputs
  44. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  45. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  46. END_DATADESC();
  47. LINK_ENTITY_TO_CLASS(info_player_allies, CSpawnPoint);
  48. LINK_ENTITY_TO_CLASS(info_player_axis, CSpawnPoint);
  49. #endif
  50. REGISTER_GAMERULES_CLASS( CDODGameRules );
  51. #define MAX_RESPAWN_WAVES_TO_TRANSMIT 5
  52. #ifdef CLIENT_DLL
  53. void RecvProxy_RoundState( const CRecvProxyData *pData, void *pStruct, void *pOut )
  54. {
  55. CDODGameRules *pGamerules = ( CDODGameRules *)pStruct;
  56. int iRoundState = pData->m_Value.m_Int;
  57. pGamerules->SetRoundState( iRoundState );
  58. }
  59. #endif
  60. BEGIN_NETWORK_TABLE_NOBASE( CDODGameRules, DT_DODGameRules )
  61. #ifdef CLIENT_DLL
  62. RecvPropInt( RECVINFO( m_iRoundState ), 0, RecvProxy_RoundState ),
  63. RecvPropBool( RECVINFO( m_bInWarmup ) ),
  64. RecvPropBool( RECVINFO( m_bAwaitingReadyRestart ) ),
  65. RecvPropTime( RECVINFO( m_flMapResetTime ) ),
  66. RecvPropTime( RECVINFO( m_flRestartRoundTime ) ),
  67. RecvPropBool( RECVINFO( m_bAlliesAreBombing ) ),
  68. RecvPropBool( RECVINFO( m_bAxisAreBombing ) ),
  69. RecvPropArray3( RECVINFO_ARRAY(m_AlliesRespawnQueue), RecvPropTime( RECVINFO(m_AlliesRespawnQueue[0]) ) ),
  70. RecvPropArray3( RECVINFO_ARRAY(m_AxisRespawnQueue), RecvPropTime( RECVINFO(m_AxisRespawnQueue[0]) ) ),
  71. RecvPropInt( RECVINFO( m_iAlliesRespawnHead ) ),
  72. RecvPropInt( RECVINFO( m_iAlliesRespawnTail ) ),
  73. RecvPropInt( RECVINFO( m_iAxisRespawnHead ) ),
  74. RecvPropInt( RECVINFO( m_iAxisRespawnTail ) ),
  75. #else
  76. SendPropInt( SENDINFO( m_iRoundState ), 5 ),
  77. SendPropBool( SENDINFO( m_bInWarmup ) ),
  78. SendPropBool( SENDINFO( m_bAwaitingReadyRestart ) ),
  79. SendPropTime( SENDINFO( m_flMapResetTime ) ),
  80. SendPropTime( SENDINFO( m_flRestartRoundTime ) ),
  81. SendPropBool( SENDINFO( m_bAlliesAreBombing ) ),
  82. SendPropBool( SENDINFO( m_bAxisAreBombing ) ),
  83. SendPropArray3( SENDINFO_ARRAY3(m_AlliesRespawnQueue), SendPropTime( SENDINFO_ARRAY(m_AlliesRespawnQueue) ) ),
  84. SendPropArray3( SENDINFO_ARRAY3(m_AxisRespawnQueue), SendPropTime( SENDINFO_ARRAY(m_AxisRespawnQueue) ) ),
  85. SendPropInt( SENDINFO( m_iAlliesRespawnHead ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ),
  86. SendPropInt( SENDINFO( m_iAlliesRespawnTail ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ),
  87. SendPropInt( SENDINFO( m_iAxisRespawnHead ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ),
  88. SendPropInt( SENDINFO( m_iAxisRespawnTail ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ),
  89. #endif
  90. END_NETWORK_TABLE()
  91. #ifndef CLIENT_DLL
  92. ConVar dod_flagrespawnbonus( "dod_flagrespawnbonus", "1.0", FCVAR_GAMEDLL | FCVAR_CHEAT, "How many seconds per advantage flag to decrease the respawn time" );
  93. ConVar mp_warmup_time( "mp_warmup_time", "0", FCVAR_GAMEDLL, "Warmup time length in seconds" );
  94. ConVar mp_restartwarmup( "mp_restartwarmup", "0", FCVAR_GAMEDLL, "Set to 1 to start or restart the warmup period." );
  95. ConVar mp_cancelwarmup( "mp_cancelwarmup", "0", FCVAR_GAMEDLL, "Set to 1 to end the warmup period." );
  96. #endif
  97. ConVar dod_enableroundwaittime( "dod_enableroundwaittime", "1", FCVAR_REPLICATED, "Enable timers to wait between rounds." );
  98. ConVar mp_allowrandomclass( "mp_allowrandomclass", "1", FCVAR_REPLICATED, "Allow players to select random class" );
  99. ConVar dod_bonusroundtime( "dod_bonusroundtime", "15", FCVAR_REPLICATED, "Time after round win until round restarts", true, 5, true, 15 );
  100. LINK_ENTITY_TO_CLASS( dod_gamerules, CDODGameRulesProxy );
  101. IMPLEMENT_NETWORKCLASS_ALIASED( DODGameRulesProxy, DT_DODGameRulesProxy )
  102. #ifdef CLIENT_DLL
  103. void RecvProxy_DODGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID )
  104. {
  105. CDODGameRules *pRules = DODGameRules();
  106. Assert( pRules );
  107. *pOut = pRules;
  108. }
  109. BEGIN_RECV_TABLE( CDODGameRulesProxy, DT_DODGameRulesProxy )
  110. RecvPropDataTable( "dod_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_DODGameRules ), RecvProxy_DODGameRules )
  111. END_RECV_TABLE()
  112. #else
  113. void* SendProxy_DODGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID )
  114. {
  115. CDODGameRules *pRules = DODGameRules();
  116. Assert( pRules );
  117. pRecipients->SetAllRecipients();
  118. return pRules;
  119. }
  120. BEGIN_SEND_TABLE( CDODGameRulesProxy, DT_DODGameRulesProxy )
  121. SendPropDataTable( "dod_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_DODGameRules ), SendProxy_DODGameRules )
  122. END_SEND_TABLE()
  123. #endif
  124. static CDODViewVectors g_DODViewVectors(
  125. Vector( 0, 0, 58 ), //VEC_VIEW (m_vView)
  126. Vector(-16, -16, 0 ), //VEC_HULL_MIN (m_vHullMin)
  127. Vector( 16, 16, 72 ), //VEC_HULL_MAX (m_vHullMax)
  128. Vector(-16, -16, 0 ), //VEC_DUCK_HULL_MIN (m_vDuckHullMin)
  129. Vector( 16, 16, 45 ), //VEC_DUCK_HULL_MAX (m_vDuckHullMax)
  130. Vector( 0, 0, 34 ), //VEC_DUCK_VIEW (m_vDuckView)
  131. Vector(-10, -10, -10 ), //VEC_OBS_HULL_MIN (m_vObsHullMin)
  132. Vector( 10, 10, 10 ), //VEC_OBS_HULL_MAX (m_vObsHullMax)
  133. Vector( 0, 0, 14 ), //VEC_DEAD_VIEWHEIGHT (m_vDeadViewHeight)
  134. Vector(-16, -16, 0 ), //VEC_PRONE_HULL_MIN (m_vProneHullMin)
  135. Vector( 16, 16, 24 ) //VEC_PRONE_HULL_MAX (m_vProneHullMax)
  136. );
  137. #ifdef CLIENT_DLL
  138. #else
  139. void ParseEntKVBlock( CBaseEntity *pNode, KeyValues *pkvNode )
  140. {
  141. KeyValues *pkvNodeData = pkvNode->GetFirstSubKey();
  142. while ( pkvNodeData )
  143. {
  144. // Handle the connections block
  145. if ( !Q_strcmp(pkvNodeData->GetName(), "connections") )
  146. {
  147. ParseEntKVBlock( pNode, pkvNodeData );
  148. }
  149. else
  150. {
  151. pNode->KeyValue( pkvNodeData->GetName(), pkvNodeData->GetString() );
  152. }
  153. pkvNodeData = pkvNodeData->GetNextKey();
  154. }
  155. }
  156. CUtlVector<EHANDLE> m_hSpawnedEntities;
  157. // for now only allow blocker walls to load this way
  158. bool CanLoadEntityFromEntText( const char *clsName )
  159. {
  160. if ( !Q_strcmp( clsName, "func_team_wall" ) )
  161. {
  162. return true;
  163. }
  164. return false;
  165. }
  166. void Load_EntText( void )
  167. {
  168. bool oldLock = engine->LockNetworkStringTables( false );
  169. // remove all ents in m_SpawnedEntities
  170. for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ )
  171. {
  172. UTIL_Remove( m_hSpawnedEntities[i] );
  173. }
  174. // delete the items from our list
  175. m_hSpawnedEntities.RemoveAll();
  176. // Find the commentary file
  177. char szFullName[512];
  178. Q_snprintf(szFullName,sizeof(szFullName), "maps/%s.ent", STRING( gpGlobals->mapname ));
  179. KeyValues *pkvFile = new KeyValues( "EntText" );
  180. if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) )
  181. {
  182. DevMsg( "Load_EntText: Loading entity data from %s. \n", szFullName );
  183. // Load each commentary block, and spawn the entities
  184. KeyValues *pkvNode = pkvFile->GetFirstSubKey();
  185. while ( pkvNode )
  186. {
  187. // Get node name
  188. const char *pNodeName = pkvNode->GetName();
  189. KeyValues *pClassname = pkvNode->FindKey( "classname" );
  190. if ( pClassname )
  191. {
  192. // Use the classname instead
  193. pNodeName = pClassname->GetString();
  194. }
  195. if ( CanLoadEntityFromEntText( pNodeName ) )
  196. {
  197. // Spawn the entity
  198. CBaseEntity *pNode = CreateEntityByName( pNodeName );
  199. if ( pNode )
  200. {
  201. ParseEntKVBlock( pNode, pkvNode );
  202. DispatchSpawn( pNode );
  203. EHANDLE hHandle;
  204. hHandle = pNode;
  205. m_hSpawnedEntities.AddToTail( hHandle );
  206. }
  207. else
  208. {
  209. Warning("Load_EntText: Failed to spawn entity, type: '%s'\n", pNodeName );
  210. }
  211. }
  212. // Move to next entity
  213. pkvNode = pkvNode->GetNextKey();
  214. }
  215. // Then activate all the entities
  216. for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ )
  217. {
  218. m_hSpawnedEntities[i]->Activate();
  219. }
  220. }
  221. else
  222. {
  223. DevMsg( "Load_EntText: Could not find entity data file '%s'. \n", szFullName );
  224. }
  225. engine->LockNetworkStringTables( oldLock );
  226. }
  227. // --------------------------------------------------------------------------------------------------- //
  228. // Voice helper
  229. // --------------------------------------------------------------------------------------------------- //
  230. class CVoiceGameMgrHelper : public IVoiceGameMgrHelper
  231. {
  232. public:
  233. virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity )
  234. {
  235. // Dead players can only be heard by other dead team mates
  236. if ( pTalker->IsAlive() == false )
  237. {
  238. if ( pListener->IsAlive() == false )
  239. return ( pListener->InSameTeam( pTalker ) );
  240. return false;
  241. }
  242. return ( pListener->InSameTeam( pTalker ) );
  243. }
  244. };
  245. CVoiceGameMgrHelper g_VoiceGameMgrHelper;
  246. IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper;
  247. // --------------------------------------------------------------------------------------------------- //
  248. // Globals.
  249. // --------------------------------------------------------------------------------------------------- //
  250. // NOTE: the indices here must match TEAM_TERRORIST, TEAM_ALLIES, TEAM_AXIS, etc.
  251. char *sTeamNames[] =
  252. {
  253. "Unassigned",
  254. "Spectator",
  255. "Allies",
  256. "Axis"
  257. };
  258. static const char *s_PreserveEnts[] =
  259. {
  260. "player",
  261. "viewmodel",
  262. "worldspawn",
  263. "soundent",
  264. "ai_network",
  265. "ai_hint",
  266. "dod_gamerules",
  267. "dod_team_manager",
  268. "dod_player_manager",
  269. "dod_objective_resource",
  270. "env_soundscape",
  271. "env_soundscape_proxy",
  272. "env_soundscape_triggerable",
  273. "env_sprite",
  274. "env_sun",
  275. "env_wind",
  276. "env_fog_controller",
  277. "func_brush",
  278. "func_wall",
  279. "func_illusionary",
  280. "info_node",
  281. "info_target",
  282. "info_node_hint",
  283. "info_player_allies",
  284. "info_player_axis",
  285. "point_viewcontrol",
  286. "shadow_control",
  287. "sky_camera",
  288. "scene_manager",
  289. "trigger_soundscape",
  290. "info_dod_detect",
  291. "dod_team_allies",
  292. "dod_team_axis",
  293. "point_commentary_node",
  294. "dod_round_timer",
  295. "func_precipitation",
  296. "func_team_wall",
  297. "", // END Marker
  298. };
  299. // --------------------------------------------------------------------------------------------------- //
  300. // Global helper functions.
  301. // --------------------------------------------------------------------------------------------------- //
  302. // World.cpp calls this but we don't use it in DoD.
  303. void InitBodyQue()
  304. {
  305. }
  306. // Handler for the "bot" command.
  307. CON_COMMAND_F( bot, "Add a bot.", FCVAR_CHEAT )
  308. {
  309. //CDODPlayer *pPlayer = CDODPlayer::Instance( UTIL_GetCommandClientIndex() );
  310. // The bot command uses switches like command-line switches.
  311. // -count <count> tells how many bots to spawn.
  312. // -team <index> selects the bot's team. Default is -1 which chooses randomly.
  313. // Note: if you do -team !, then it
  314. // -class <index> selects the bot's class. Default is -1 which chooses randomly.
  315. // -frozen prevents the bots from running around when they spawn in.
  316. // Look at -count.
  317. int count = args.FindArgInt( "-count", 1 );
  318. count = clamp( count, 1, 16 );
  319. int iTeam = TEAM_ALLIES;
  320. const char *pVal = args.FindArg( "-team" );
  321. if ( pVal )
  322. {
  323. iTeam = atoi( pVal );
  324. iTeam = clamp( iTeam, 0, (GetNumberOfTeams()-1) );
  325. }
  326. int iClass = 0;
  327. pVal = args.FindArg( "-class" );
  328. if ( pVal )
  329. {
  330. iClass = atoi( pVal );
  331. iClass = clamp( iClass, 0, 10 );
  332. }
  333. // Look at -frozen.
  334. bool bFrozen = !!args.FindArg( "-frozen" );
  335. // Ok, spawn all the bots.
  336. while ( --count >= 0 )
  337. {
  338. BotPutInServer( bFrozen, iTeam, iClass );
  339. }
  340. }
  341. void RestartRound_f()
  342. {
  343. DODGameRules()->State_Transition( STATE_RESTART );
  344. }
  345. ConCommand cc_Restart( "restartround", RestartRound_f, "Restart the round", FCVAR_CHEAT );
  346. void CDODGameRules::CopyGamePlayLogic( const CDODGamePlayRules otherGamePlay )
  347. {
  348. m_GamePlayRules.CopyFrom( otherGamePlay );
  349. }
  350. // --------------------------------------------------------------------------------------------------- //
  351. // CDODGameRules implementation.
  352. // --------------------------------------------------------------------------------------------------- //
  353. CDODGameRules::CDODGameRules()
  354. {
  355. InitTeams();
  356. ResetMapTime();
  357. m_GamePlayRules.Reset();
  358. ResetScores();
  359. m_bInWarmup = false;
  360. m_bAwaitingReadyRestart = false;
  361. m_flRestartRoundTime = -1;
  362. m_iAlliesRespawnHead = 0;
  363. m_iAlliesRespawnTail = 0;
  364. m_iAxisRespawnHead = 0;
  365. m_iAxisRespawnTail = 0;
  366. m_iNumAlliesRespawnWaves = 0;
  367. m_iNumAxisRespawnWaves = 0;
  368. for ( int i=0; i <DOD_RESPAWN_QUEUE_SIZE; i++ )
  369. {
  370. m_AlliesRespawnQueue.Set( i, 0 );
  371. m_AxisRespawnQueue.Set( i, 0 );
  372. }
  373. m_bLevelInitialized = false;
  374. m_iSpawnPointCount_Allies = 0;
  375. m_iSpawnPointCount_Axis = 0;
  376. Q_memset( m_vecPlayerPositions,0, sizeof(m_vecPlayerPositions) );
  377. // Lets execute a map specific cfg file
  378. // Matt - execute this after server.cfg!
  379. char szCommand[256] = { 0 };
  380. // Map names cannot contain quotes or control characters so this is safe but silly that we have to do it.
  381. Q_snprintf( szCommand, sizeof(szCommand), "exec \"%s.cfg\"\n", STRING(gpGlobals->mapname) );
  382. engine->ServerCommand( szCommand );
  383. m_pCurStateInfo = NULL;
  384. State_Transition( STATE_PREGAME );
  385. // stats
  386. memset( m_iStatsKillsPerClass_Allies, 0, sizeof(m_iStatsKillsPerClass_Allies) );
  387. memset( m_iStatsKillsPerClass_Axis, 0, sizeof(m_iStatsKillsPerClass_Axis) );
  388. memset( m_iStatsSpawnsPerClass_Allies, 0, sizeof(m_iStatsSpawnsPerClass_Allies) );
  389. memset( m_iStatsSpawnsPerClass_Axis, 0, sizeof(m_iStatsSpawnsPerClass_Axis) );
  390. memset( m_iStatsCapsPerClass_Allies, 0, sizeof(m_iStatsCapsPerClass_Allies) );
  391. memset( m_iStatsCapsPerClass_Axis, 0, sizeof(m_iStatsCapsPerClass_Axis) );
  392. memset( m_iStatsDefensesPerClass_Allies, 0, sizeof(m_iStatsDefensesPerClass_Allies) );
  393. memset( m_iStatsDefensesPerClass_Axis, 0, sizeof(m_iStatsDefensesPerClass_Axis) );
  394. memset( &m_iWeaponShotsFired, 0, sizeof(m_iWeaponShotsFired) );
  395. memset( &m_iWeaponShotsHit, 0, sizeof(m_iWeaponShotsHit) );
  396. memset( &m_iWeaponDistanceBuckets, 0, sizeof(m_iWeaponDistanceBuckets) );
  397. memset( &m_flSecondsPlayedPerClass_Allies, 0, sizeof(m_flSecondsPlayedPerClass_Allies) );
  398. memset( &m_flSecondsPlayedPerClass_Axis, 0, sizeof(m_flSecondsPlayedPerClass_Axis) );
  399. m_bUsingTimer = false;
  400. m_pRoundTimer = NULL; // created on first round spawn that requires a timer
  401. m_bAlliesAreBombing = false;
  402. m_bAxisAreBombing = false;
  403. m_flNextFailSafeWaveCheckTime = 0;
  404. // Init the holiday
  405. int day = 0, month = 0, year = 0;
  406. #ifdef WIN32
  407. GetCurrentDate( &day, &month, &year );
  408. #elif POSIX
  409. time_t now = time(NULL);
  410. struct tm *tm = localtime( &now );
  411. day = tm->tm_mday + 1;
  412. month = tm->tm_mon;
  413. year = tm->tm_year + 1900;
  414. #endif
  415. if ( ( month == 12 && day >= 1 ) || ( month == 1 && day <= 2 ) )
  416. {
  417. m_bWinterHolidayActive = true;
  418. }
  419. else
  420. {
  421. m_bWinterHolidayActive = false;
  422. }
  423. }
  424. //-----------------------------------------------------------------------------
  425. // Purpose:
  426. //-----------------------------------------------------------------------------
  427. CDODGameRules::~CDODGameRules()
  428. {
  429. // Note, don't delete each team since they are in the gEntList and will
  430. // automatically be deleted from there, instead.
  431. g_Teams.Purge();
  432. }
  433. void CDODGameRules::LevelShutdown( void )
  434. {
  435. UploadLevelStats();
  436. BaseClass::LevelShutdown();
  437. }
  438. #define MY_USHRT_MAX 0xffff
  439. #define MY_UCHAR_MAX 0xff
  440. void CDODGameRules::UploadLevelStats( void )
  441. {
  442. if ( Q_strlen( STRING( gpGlobals->mapname ) ) > 0 )
  443. {
  444. int i,j;
  445. CDODTeam *pAllies = GetGlobalDODTeam( TEAM_ALLIES );
  446. CDODTeam *pAxis = GetGlobalDODTeam( TEAM_AXIS );
  447. dod_gamestats_t stats;
  448. memset( &stats, 0, sizeof(stats) );
  449. // Header
  450. stats.header.iVersion = DOD_STATS_BLOB_VERSION;
  451. Q_strncpy( stats.header.szGameName, "dod", sizeof(stats.header.szGameName) );
  452. Q_strncpy( stats.header.szMapName, STRING( gpGlobals->mapname ), sizeof( stats.header.szMapName ) );
  453. ConVar *hostip = cvar->FindVar( "hostip" );
  454. if ( hostip )
  455. {
  456. int ip = hostip->GetInt();
  457. stats.header.ipAddr[0] = ip >> 24;
  458. stats.header.ipAddr[1] = ( ip >> 16 ) & 0xff;
  459. stats.header.ipAddr[2] = ( ip >> 8 ) & 0xff;
  460. stats.header.ipAddr[3] = ( ip ) & 0xff;
  461. }
  462. ConVar *hostport = cvar->FindVar( "hostip" );
  463. if ( hostport )
  464. {
  465. stats.header.port = hostport->GetInt();
  466. }
  467. stats.header.serverid = 0;
  468. stats.iMinutesPlayed = clamp( (short)( gpGlobals->curtime / 60 ), 0, MY_USHRT_MAX );
  469. // Team Scores
  470. stats.iNumAlliesWins = clamp( pAllies->GetRoundsWon(), 0, MY_UCHAR_MAX );
  471. stats.iNumAxisWins = clamp( pAxis->GetRoundsWon(), 0, MY_UCHAR_MAX );
  472. stats.iAlliesTickPoints = clamp( pAllies->GetScore(), 0, MY_USHRT_MAX );
  473. stats.iAxisTickPoints = clamp( pAxis->GetScore(), 0, MY_USHRT_MAX );
  474. // Player Data
  475. for ( i=1;i<=MAX_PLAYERS;i++ )
  476. {
  477. CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  478. if ( pPlayer )
  479. {
  480. // Sum up the time played for players that are still connected.
  481. // players who disconnected had their connect time added in ClientDisconnected
  482. // Tally the latest time for this player
  483. pPlayer->TallyLatestTimePlayedPerClass( pPlayer->GetTeamNumber(), pPlayer->m_Shared.DesiredPlayerClass() );
  484. for( j=0;j<7;j++ )
  485. {
  486. m_flSecondsPlayedPerClass_Allies[j] += pPlayer->m_flTimePlayedPerClass_Allies[j];
  487. m_flSecondsPlayedPerClass_Axis[j] += pPlayer->m_flTimePlayedPerClass_Axis[j];
  488. }
  489. }
  490. }
  491. // convert to minutes
  492. for( j=0;j<7;j++ )
  493. {
  494. stats.iMinutesPlayedPerClass_Allies[j] = clamp( (short)( m_flSecondsPlayedPerClass_Allies[j] / 60 ), 0, MY_USHRT_MAX );
  495. stats.iMinutesPlayedPerClass_Axis[j] = clamp( (short)( m_flSecondsPlayedPerClass_Axis[j] / 60 ), 0, MY_USHRT_MAX );
  496. }
  497. for ( i=0;i<6;i++ )
  498. {
  499. stats.iKillsPerClass_Allies[i] = clamp( (short)m_iStatsKillsPerClass_Allies[i], 0, MY_USHRT_MAX );
  500. stats.iKillsPerClass_Axis[i] = clamp( (short)m_iStatsKillsPerClass_Axis[i], 0, MY_USHRT_MAX );
  501. stats.iSpawnsPerClass_Allies[i] = clamp( (short)m_iStatsSpawnsPerClass_Allies[i], 0, MY_USHRT_MAX );
  502. stats.iSpawnsPerClass_Axis[i] = clamp( (short)m_iStatsSpawnsPerClass_Axis[i], 0, MY_USHRT_MAX );
  503. stats.iCapsPerClass_Allies[i] = clamp( (short)m_iStatsCapsPerClass_Allies[i], 0, MY_USHRT_MAX );
  504. stats.iCapsPerClass_Axis[i] = clamp( (short)m_iStatsCapsPerClass_Axis[i], 0, MY_USHRT_MAX );
  505. stats.iDefensesPerClass_Allies[i] = clamp( m_iStatsDefensesPerClass_Allies[i], 0, MY_UCHAR_MAX );
  506. stats.iDefensesPerClass_Axis[i] = clamp( m_iStatsDefensesPerClass_Axis[i], 0, MY_UCHAR_MAX );
  507. }
  508. // Server Settings
  509. stats.iClassLimits_Allies[0] = clamp( mp_limitAlliesRifleman.GetInt(), -1, 254 );
  510. stats.iClassLimits_Allies[1] = clamp( mp_limitAlliesAssault.GetInt(), -1, 254 );
  511. stats.iClassLimits_Allies[2] = clamp( mp_limitAlliesSupport.GetInt(), -1, 254 );
  512. stats.iClassLimits_Allies[3] = clamp( mp_limitAlliesSniper.GetInt(), -1, 254 );
  513. stats.iClassLimits_Allies[4] = clamp( mp_limitAlliesMachinegun.GetInt(), -1, 254 );
  514. stats.iClassLimits_Allies[5] = clamp( mp_limitAlliesRocket.GetInt(), -1, 254 );
  515. stats.iClassLimits_Axis[0] = clamp( mp_limitAxisRifleman.GetInt(), -1, 254 );
  516. stats.iClassLimits_Axis[1] = clamp( mp_limitAxisAssault.GetInt(), -1, 254 );
  517. stats.iClassLimits_Axis[2] = clamp( mp_limitAxisSupport.GetInt(), -1, 254 );
  518. stats.iClassLimits_Axis[3] = clamp( mp_limitAxisSniper.GetInt(), -1, 254 );
  519. stats.iClassLimits_Axis[4] = clamp( mp_limitAxisMachinegun.GetInt(), -1, 254 );
  520. stats.iClassLimits_Axis[5] = clamp( mp_limitAxisRocket.GetInt(), -1, 254 );
  521. // Weapon Data
  522. // Send hit/shots/distance info for the following guns / modes
  523. for ( i=0;i<DOD_NUM_DISTANCE_STAT_WEAPONS;i++ )
  524. {
  525. int weaponId = iDistanceStatWeapons[i];
  526. stats.weaponStatsDistance[i].iNumHits = clamp( m_iWeaponShotsHit[weaponId], 0, MY_USHRT_MAX );
  527. stats.weaponStatsDistance[i].iNumAttacks = clamp( m_iWeaponShotsFired[weaponId], 0, MY_USHRT_MAX );
  528. for ( int j=0;j<DOD_NUM_WEAPON_DISTANCE_BUCKETS;j++ )
  529. {
  530. stats.weaponStatsDistance[i].iDistanceBuckets[j] = clamp( m_iWeaponDistanceBuckets[weaponId][j], 0, MY_USHRT_MAX );
  531. }
  532. }
  533. // Send hit/shots info for the following guns / modes
  534. for ( i=0;i<DOD_NUM_NODIST_STAT_WEAPONS;i++ )
  535. {
  536. int weaponId = iNoDistStatWeapons[i];
  537. stats.weaponStats[i].iNumHits = clamp( m_iWeaponShotsHit[weaponId], 0, MY_USHRT_MAX );
  538. stats.weaponStats[i].iNumAttacks = clamp( m_iWeaponShotsFired[weaponId], 0, MY_USHRT_MAX );
  539. }
  540. const void *pvBlobData = ( const void * )( &stats );
  541. unsigned int uBlobSize = sizeof( stats );
  542. if ( gamestatsuploader )
  543. {
  544. gamestatsuploader->UploadGameStats(
  545. STRING( gpGlobals->mapname ),
  546. DOD_STATS_BLOB_VERSION,
  547. uBlobSize,
  548. pvBlobData );
  549. }
  550. }
  551. }
  552. void CDODGameRules::Stats_PlayerKill( int team, int cls )
  553. {
  554. Assert( cls >= 0 && cls <= 5 );
  555. if ( cls >= 0 && cls <= 5 )
  556. {
  557. if ( team == TEAM_ALLIES )
  558. m_iStatsKillsPerClass_Allies[cls]++;
  559. else if ( team == TEAM_AXIS )
  560. m_iStatsKillsPerClass_Axis[cls]++;
  561. }
  562. }
  563. void CDODGameRules::Stats_PlayerCap( int team, int cls )
  564. {
  565. Assert( cls >= 0 && cls <= 5 );
  566. if ( cls >= 0 && cls <= 5 )
  567. {
  568. if ( team == TEAM_ALLIES )
  569. m_iStatsCapsPerClass_Allies[cls]++;
  570. else if ( team == TEAM_AXIS )
  571. m_iStatsCapsPerClass_Axis[cls]++;
  572. }
  573. }
  574. void CDODGameRules::Stats_PlayerDefended( int team, int cls )
  575. {
  576. Assert( cls >= 0 && cls <= 5 );
  577. if ( cls >= 0 && cls <= 5 )
  578. {
  579. if ( team == TEAM_ALLIES )
  580. m_iStatsDefensesPerClass_Allies[cls]++;
  581. else if ( team == TEAM_AXIS )
  582. m_iStatsDefensesPerClass_Axis[cls]++;
  583. }
  584. }
  585. void CDODGameRules::Stats_WeaponFired( int weaponID )
  586. {
  587. m_iWeaponShotsFired[weaponID]++;
  588. }
  589. void CDODGameRules::Stats_WeaponHit( int weaponID, float flDist )
  590. {
  591. m_iWeaponShotsHit[weaponID]++;
  592. int bucket = Stats_WeaponDistanceToBucket( weaponID, flDist );
  593. m_iWeaponDistanceBuckets[weaponID][bucket]++;
  594. }
  595. int CDODGameRules::Stats_WeaponDistanceToBucket( int weaponID, float flDist )
  596. {
  597. int bucket = 4;
  598. int iDist = (int)flDist;
  599. for ( int i=0;i<DOD_NUM_WEAPON_DISTANCE_BUCKETS-1;i++ )
  600. {
  601. if ( iDist < iWeaponBucketDistances[i] )
  602. {
  603. bucket = i;
  604. break;
  605. }
  606. }
  607. return bucket;
  608. }
  609. //-----------------------------------------------------------------------------
  610. // Purpose: DoD Specific Client Commands
  611. // Input :
  612. // Output :
  613. //-----------------------------------------------------------------------------
  614. bool CDODGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args )
  615. {
  616. CDODPlayer *pPlayer = ToDODPlayer( pEdict );
  617. const char *pcmd = args[0];
  618. #ifdef DEBUG
  619. if ( FStrEq( pcmd, "teamwin" ) )
  620. {
  621. if ( args.ArgC() < 2 )
  622. return true;
  623. SetWinningTeam( atoi( args[1] ) );
  624. return true;
  625. }
  626. else
  627. #endif
  628. // Handle some player commands here as they relate more directly to gamerules state
  629. if ( FStrEq( pcmd, "nextmap" ) )
  630. {
  631. CDODPlayer *pDODPlayer = ToDODPlayer(pPlayer);
  632. if ( pDODPlayer->m_flNextTimeCheck < gpGlobals->curtime )
  633. {
  634. char szNextMap[32];
  635. if ( nextlevel.GetString() && *nextlevel.GetString() )
  636. {
  637. Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
  638. }
  639. else
  640. {
  641. GetNextLevelName( szNextMap, sizeof( szNextMap ) );
  642. }
  643. ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", szNextMap);
  644. pDODPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1;
  645. }
  646. return true;
  647. }
  648. else if ( FStrEq( pcmd, "timeleft" ) )
  649. {
  650. CDODPlayer *pDODPlayer = ToDODPlayer(pPlayer);
  651. if ( pDODPlayer->m_flNextTimeCheck < gpGlobals->curtime )
  652. {
  653. if ( mp_timelimit.GetInt() > 0 )
  654. {
  655. int iTimeLeft = GetTimeLeft();
  656. char szMinutes[5];
  657. char szSeconds[3];
  658. if ( iTimeLeft <= 0 )
  659. {
  660. Q_snprintf( szMinutes, sizeof(szMinutes), "0" );
  661. Q_snprintf( szSeconds, sizeof(szSeconds), "00" );
  662. }
  663. else
  664. {
  665. Q_snprintf( szMinutes, sizeof(szMinutes), "%d", iTimeLeft / 60 );
  666. Q_snprintf( szSeconds, sizeof(szSeconds), "%02d", iTimeLeft % 60 );
  667. }
  668. ClientPrint( pPlayer, HUD_PRINTTALK, "#game_time_left1", szMinutes, szSeconds );
  669. }
  670. else
  671. {
  672. ClientPrint( pPlayer, HUD_PRINTTALK, "#game_time_left2" );
  673. }
  674. CDODPlayer *pDODPlayer = ToDODPlayer(pPlayer);
  675. pDODPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1;
  676. }
  677. return true;
  678. }
  679. else if ( pPlayer->ClientCommand( args ) )
  680. {
  681. return true;
  682. }
  683. else if ( BaseClass::ClientCommand( pEdict, args ) )
  684. {
  685. return true;
  686. }
  687. return false;
  688. }
  689. void CDODGameRules::CheckChatForReadySignal( CDODPlayer *pPlayer, const char *chatmsg )
  690. {
  691. if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_clan_ready_signal.GetString() ) )
  692. {
  693. if( !m_bHeardAlliesReady && pPlayer->GetTeamNumber() == TEAM_ALLIES )
  694. {
  695. m_bHeardAlliesReady = true;
  696. IGameEvent *event = gameeventmanager->CreateEvent( "dod_allies_ready" );
  697. if ( event )
  698. gameeventmanager->FireEvent( event );
  699. }
  700. else if( !m_bHeardAxisReady && pPlayer->GetTeamNumber() == TEAM_AXIS )
  701. {
  702. m_bHeardAxisReady = true;
  703. IGameEvent *event = gameeventmanager->CreateEvent( "dod_axis_ready" );
  704. if ( event )
  705. gameeventmanager->FireEvent( event );
  706. }
  707. }
  708. }
  709. int CDODGameRules::SelectDefaultTeam()
  710. {
  711. int team = TEAM_UNASSIGNED;
  712. CDODTeam *pAllies = GetGlobalDODTeam(TEAM_ALLIES);
  713. CDODTeam *pAxis = GetGlobalDODTeam(TEAM_AXIS);
  714. int iNumAllies = pAllies->GetNumPlayers();
  715. int iNumAxis = pAxis->GetNumPlayers();
  716. int iAlliesRoundsWon = pAllies->GetRoundsWon();
  717. int iAxisRoundsWon = pAxis->GetRoundsWon();
  718. int iAlliesPoints = pAllies->GetScore();
  719. int iAxisPoints = pAxis->GetScore();
  720. // Choose the team that's lacking players
  721. if ( iNumAllies < iNumAxis )
  722. {
  723. team = TEAM_ALLIES;
  724. }
  725. else if ( iNumAllies > iNumAxis )
  726. {
  727. team = TEAM_AXIS;
  728. }
  729. // Choose the team that's losing
  730. else if ( iAlliesRoundsWon < iAxisRoundsWon )
  731. {
  732. team = TEAM_ALLIES;
  733. }
  734. else if ( iAlliesRoundsWon > iAxisRoundsWon )
  735. {
  736. team = TEAM_AXIS;
  737. }
  738. // choose the team with fewer points
  739. else if ( iAlliesPoints < iAxisPoints )
  740. {
  741. team = TEAM_ALLIES;
  742. }
  743. else if ( iAlliesPoints > iAxisPoints )
  744. {
  745. team = TEAM_AXIS;
  746. }
  747. else
  748. {
  749. // Teams and scores are equal, pick a random team
  750. team = ( random->RandomInt(0,1) == 0 ) ? TEAM_ALLIES : TEAM_AXIS;
  751. }
  752. if ( TeamFull( team ) )
  753. {
  754. // Pick the opposite team
  755. if ( team == TEAM_ALLIES )
  756. {
  757. team = TEAM_AXIS;
  758. }
  759. else
  760. {
  761. team = TEAM_ALLIES;
  762. }
  763. // No choices left
  764. if ( TeamFull( team ) )
  765. return TEAM_UNASSIGNED;
  766. }
  767. return team;
  768. }
  769. bool CDODGameRules::TeamFull( int team_id )
  770. {
  771. switch ( team_id )
  772. {
  773. case TEAM_ALLIES:
  774. {
  775. int iNumAllies = GetGlobalDODTeam(TEAM_ALLIES)->GetNumPlayers();
  776. return iNumAllies >= m_iSpawnPointCount_Allies;
  777. }
  778. case TEAM_AXIS:
  779. {
  780. int iNumAxis = GetGlobalDODTeam(TEAM_AXIS)->GetNumPlayers();
  781. return iNumAxis >= m_iSpawnPointCount_Axis;
  782. }
  783. }
  784. return false;
  785. }
  786. //-----------------------------------------------------------------------------
  787. // Purpose: Player has just spawned. Equip them.
  788. //-----------------------------------------------------------------------------
  789. // return a multiplier that should adjust the damage done by a blast at position vecSrc to something at the position
  790. // vecEnd. This will take into account the density of an entity that blocks the line of sight from one position to
  791. // the other.
  792. //
  793. // this algorithm was taken from the HL2 version of RadiusDamage.
  794. float CDODGameRules::GetExplosionDamageAdjustment(Vector & vecSrc, Vector & vecEnd, CBaseEntity *pTarget, CBaseEntity *pEntityToIgnore)
  795. {
  796. float retval = 0.0;
  797. trace_t tr;
  798. UTIL_TraceLine(vecSrc, vecEnd, MASK_SHOT, pEntityToIgnore, COLLISION_GROUP_NONE, &tr);
  799. Assert( pTarget );
  800. // its a hit if we made it to the dest, or if we hit another part of the target on the way
  801. if (tr.fraction == 1.0 || tr.m_pEnt == pTarget )
  802. {
  803. retval = 1.0;
  804. }
  805. else if (!(tr.DidHitWorld()) && (tr.m_pEnt != NULL) && (tr.m_pEnt->GetOwnerEntity() != pEntityToIgnore))
  806. {
  807. // if we didn't hit world geometry perhaps there's still damage to be done here.
  808. CBaseEntity *blockingEntity = tr.m_pEnt;
  809. // check to see if this part of the player is visible if entities are ignored.
  810. UTIL_TraceLine(vecSrc, vecEnd, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &tr);
  811. if (tr.fraction == 1.0)
  812. {
  813. if ((blockingEntity != NULL) && (blockingEntity->VPhysicsGetObject() != NULL))
  814. {
  815. int nMaterialIndex = blockingEntity->VPhysicsGetObject()->GetMaterialIndex();
  816. float flDensity;
  817. float flThickness;
  818. float flFriction;
  819. float flElasticity;
  820. physprops->GetPhysicsProperties( nMaterialIndex, &flDensity,
  821. &flThickness, &flFriction, &flElasticity );
  822. const float ONE_OVER_DENSITY_ABSORB_ALL_DAMAGE = ( 1.0 / 3000.0 );
  823. float scale = flDensity * ONE_OVER_DENSITY_ABSORB_ALL_DAMAGE;
  824. if ((scale >= 0.0) && (scale < 1.0))
  825. {
  826. retval = 1.0 - scale;
  827. }
  828. else if (scale < 0.0)
  829. {
  830. // should never happen, but just in case.
  831. retval = 1.0;
  832. }
  833. }
  834. else
  835. {
  836. retval = 0.75; // we're blocked by something that isn't an entity with a physics module or world geometry, just cut damage in half for now.
  837. }
  838. }
  839. }
  840. return retval;
  841. }
  842. // returns the percentage of the player that is visible from the given point in the world.
  843. // return value is between 0 and 1.
  844. float CDODGameRules::GetAmountOfEntityVisible(Vector & vecSrc, CBaseEntity *entity, CBaseEntity *pIgnoreEntity )
  845. {
  846. float retval = 0.0;
  847. Vector vecHullSizeNormal = VEC_HULL_MAX - VEC_HULL_MIN;
  848. const float damagePercentageChest = 0.40;
  849. const float damagePercentageHead = 0.30;
  850. const float damagePercentageFoot = 0.10; // x 2
  851. const float damagePercentageHand = 0.05; // x 2
  852. if (!(entity->IsPlayer()))
  853. {
  854. // the entity is not a player, so the damage is all or nothing.
  855. Vector vecTarget;
  856. vecTarget = entity->BodyTarget(vecSrc, false);
  857. return GetExplosionDamageAdjustment(vecSrc, vecTarget, entity, pIgnoreEntity);
  858. }
  859. CDODPlayer *player = ToDODPlayer(entity);
  860. /*
  861. new, sane method
  862. */
  863. static int iRHandIndex = 0;
  864. static int iLHandIndex = 0;
  865. static int iHeadIndex = 0;
  866. static int iChestIndex = 0;
  867. static int iRFootIndex = 0;
  868. static int iLFootIndex = 0;
  869. static bool bInitializedBones = false;
  870. if ( !bInitializedBones )
  871. {
  872. iRHandIndex = player->LookupBone( "ValveBiped.Bip01_R_Hand" );
  873. iLHandIndex = player->LookupBone( "ValveBiped.Bip01_L_Hand" );
  874. iHeadIndex = player->LookupBone( "ValveBiped.Bip01_Head1" );
  875. iChestIndex = player->LookupBone( "ValveBiped.Bip01_Spine2" );
  876. iRFootIndex = player->LookupBone( "ValveBiped.Bip01_R_Foot" );
  877. iLFootIndex = player->LookupBone( "ValveBiped.Bip01_L_Foot" );
  878. Assert( iRHandIndex != -1 );
  879. Assert( iLHandIndex != -1 );
  880. Assert( iHeadIndex != -1 );
  881. Assert( iChestIndex != -1 );
  882. Assert( iRFootIndex != -1 );
  883. Assert( iLFootIndex != -1 );
  884. bInitializedBones = true;
  885. }
  886. #ifdef _DEBUG
  887. // verify that these bone indeces don't change
  888. int checkBoneIndex = player->LookupBone( "ValveBiped.Bip01_R_Hand" );
  889. Assert( checkBoneIndex == iRHandIndex );
  890. #endif
  891. QAngle dummyAngle;
  892. Vector vecRHand;
  893. player->GetBonePosition( iRHandIndex, vecRHand, dummyAngle );
  894. Vector vecLHand;
  895. player->GetBonePosition( iLHandIndex, vecLHand, dummyAngle );
  896. Vector vecHead;
  897. player->GetBonePosition( iHeadIndex, vecHead, dummyAngle );
  898. Vector vecChest;
  899. player->GetBonePosition( iChestIndex, vecChest, dummyAngle );
  900. Vector vecRFoot;
  901. player->GetBonePosition( iRFootIndex, vecRFoot, dummyAngle );
  902. Vector vecLFoot;
  903. player->GetBonePosition( iLFootIndex, vecLFoot, dummyAngle );
  904. // right hand
  905. float damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRHand, player, pIgnoreEntity );
  906. retval += (damagePercentageHand * damageAdjustment);
  907. /*
  908. Msg( "right hand: %.1f\n", damageAdjustment );
  909. NDebugOverlay::Line( vecSrc, vecRHand,
  910. (int)(damageAdjustment * 255.0),
  911. (int)((1.0 - damageAdjustment) * 255.0),
  912. 0,
  913. true,
  914. 10 );*/
  915. // left hand
  916. damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecLHand, player, pIgnoreEntity );
  917. retval += (damagePercentageHand * damageAdjustment);
  918. /*
  919. Msg( "left hand: %.1f\n", damageAdjustment );
  920. NDebugOverlay::Line( vecSrc, vecLHand,
  921. (int)(damageAdjustment * 255.0),
  922. (int)((1.0 - damageAdjustment) * 255.0),
  923. 0,
  924. true,
  925. 10 );*/
  926. // head
  927. damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecHead, player, pIgnoreEntity );
  928. retval += (damagePercentageHead * damageAdjustment);
  929. /*
  930. Msg( "head: %.1f\n", damageAdjustment );
  931. NDebugOverlay::Line( vecSrc, vecHead,
  932. (int)(damageAdjustment * 255.0),
  933. (int)((1.0 - damageAdjustment) * 255.0),
  934. 0,
  935. true,
  936. 10 );*/
  937. // chest
  938. damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecChest, player, pIgnoreEntity );
  939. retval += (damagePercentageChest * damageAdjustment);
  940. /*
  941. Msg( "chest: %.1f\n", damageAdjustment );
  942. NDebugOverlay::Line( vecSrc, vecChest,
  943. (int)(damageAdjustment * 255.0),
  944. (int)((1.0 - damageAdjustment) * 255.0),
  945. 0,
  946. true,
  947. 10 );*/
  948. // right foot
  949. damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRFoot, player, pIgnoreEntity );
  950. retval += (damagePercentageFoot * damageAdjustment);
  951. /*
  952. Msg( "right foot: %.1f\n", damageAdjustment );
  953. NDebugOverlay::Line( vecSrc, vecRFoot,
  954. (int)(damageAdjustment * 255.0),
  955. (int)((1.0 - damageAdjustment) * 255.0),
  956. 0,
  957. true,
  958. 10 );*/
  959. // left foot
  960. damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRFoot, player, pIgnoreEntity );
  961. retval += (damagePercentageFoot * damageAdjustment);
  962. /*
  963. Msg( "left foot: %.1f\n", damageAdjustment );
  964. NDebugOverlay::Line( vecSrc, vecRFoot,
  965. (int)(damageAdjustment * 255.0),
  966. (int)((1.0 - damageAdjustment) * 255.0),
  967. 0,
  968. true,
  969. 10 );*/
  970. // Msg( "total: %.1f\n", retval );
  971. return retval;
  972. }
  973. void CDODGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore )
  974. {
  975. RadiusDamage( info, vecSrcIn, flRadius, iClassIgnore, pEntityIgnore, false );
  976. }
  977. ConVar r_visualizeExplosion( "r_visualizeExplosion", "0", FCVAR_CHEAT );
  978. void CDODGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore, bool bIgnoreWorld /* = false */ )
  979. {
  980. CBaseEntity *pEntity = NULL;
  981. trace_t tr;
  982. float flAdjustedDamage, falloff;
  983. Vector vecSpot;
  984. Vector vecToTarget;
  985. Vector vecSrc = vecSrcIn;
  986. float flDamagePercentage;
  987. if ( flRadius )
  988. falloff = info.GetDamage() / flRadius;
  989. else
  990. falloff = 1.0;
  991. vecSrc.z += 1;// in case grenade is lying on the ground
  992. if ( r_visualizeExplosion.GetBool() )
  993. {
  994. float flLethalRange = ( info.GetDamage() - 100 ) / falloff;
  995. float flHalfDamageRange = ( info.GetDamage() - 50 ) / falloff;
  996. float flZeroDamageRange = ( info.GetDamage() ) / falloff;
  997. // draw a red sphere representing the kill area
  998. Vector dest = vecSrc;
  999. dest.x += flLethalRange;
  1000. NDebugOverlay::HorzArrow( vecSrc, dest, 10, 255, 0, 0, 255, true, 10.0 );
  1001. // yellow for 50 damage
  1002. dest = vecSrc;
  1003. dest.x += flHalfDamageRange;
  1004. NDebugOverlay::HorzArrow( vecSrc, dest, 10, 255, 255, 0, 255, true, 10.0 );
  1005. // green for > 0 damage
  1006. dest = vecSrc;
  1007. dest.x += flZeroDamageRange;
  1008. NDebugOverlay::HorzArrow( vecSrc, dest, 10, 0, 255, 0, 255, true, 10.0 );
  1009. }
  1010. // iterate on all entities in the vicinity.
  1011. for ( CEntitySphereQuery sphere( vecSrc, flRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
  1012. {
  1013. if ( pEntity->m_takedamage != DAMAGE_NO )
  1014. {
  1015. // UNDONE: this should check a damage mask, not an ignore
  1016. if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore )
  1017. continue;
  1018. if ( pEntity == pEntityIgnore )
  1019. continue;
  1020. // radius damage can only be blocked by the world
  1021. vecSpot = pEntity->BodyTarget( vecSrc );
  1022. if ( bIgnoreWorld )
  1023. {
  1024. flDamagePercentage = 1.0;
  1025. }
  1026. else
  1027. {
  1028. // get the percentage of the target entity that is visible from the
  1029. // explosion position.
  1030. flDamagePercentage = GetAmountOfEntityVisible(vecSrc, pEntity, info.GetInflictor() );
  1031. }
  1032. if (flDamagePercentage > 0.0)
  1033. {
  1034. // the explosion can 'see' this entity, so hurt them!
  1035. vecToTarget = ( vecSpot - vecSrc );
  1036. // decrease damage for an ent that's farther from the bomb.
  1037. flAdjustedDamage = vecToTarget.Length() * falloff;
  1038. flAdjustedDamage = info.GetDamage() - flAdjustedDamage;
  1039. flAdjustedDamage *= flDamagePercentage;
  1040. if ( flAdjustedDamage > 0 )
  1041. {
  1042. CTakeDamageInfo adjustedInfo = info;
  1043. adjustedInfo.SetDamage( flAdjustedDamage );
  1044. Vector dir = vecToTarget;
  1045. VectorNormalize( dir );
  1046. // If we don't have a damage force, manufacture one
  1047. if ( adjustedInfo.GetDamagePosition() == vec3_origin || adjustedInfo.GetDamageForce() == vec3_origin )
  1048. {
  1049. CalculateExplosiveDamageForce( &adjustedInfo, dir, vecSrc, 1.5 /* explosion scale! */ );
  1050. }
  1051. else
  1052. {
  1053. // Assume the force passed in is the maximum force. Decay it based on falloff.
  1054. float flForce = adjustedInfo.GetDamageForce().Length() * falloff;
  1055. adjustedInfo.SetDamageForce( dir * flForce );
  1056. adjustedInfo.SetDamagePosition( vecSrc );
  1057. }
  1058. pEntity->TakeDamage( adjustedInfo );
  1059. // Now hit all triggers along the way that respond to damage...
  1060. pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, vecSpot, dir );
  1061. }
  1062. }
  1063. }
  1064. }
  1065. }
  1066. void CDODGameRules::RadiusStun( const CTakeDamageInfo &info, const Vector &vecSrc, float flRadius )
  1067. {
  1068. CBaseEntity *pEntity = NULL;
  1069. trace_t tr;
  1070. float flAdjustedDamage, falloff;
  1071. Vector vecSpot;
  1072. Vector vecToTarget;
  1073. if ( flRadius )
  1074. falloff = info.GetDamage() / flRadius;
  1075. else
  1076. falloff = 1.0;
  1077. // ok, now send updates to all clients
  1078. CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits;
  1079. playerbits.ClearAll();
  1080. // see which players are actually in the PVS of the grenade
  1081. engine->Message_DetermineMulticastRecipients( false, vecSrc, playerbits );
  1082. // Iterate through all players that made it into playerbits, that are inside the radius
  1083. // and give them stun damage
  1084. for ( int i=0;i<MAX_PLAYERS;i++ )
  1085. {
  1086. if ( playerbits.Get(i) == false )
  1087. continue;
  1088. pEntity = UTIL_EntityByIndex( i+1 );
  1089. if ( !pEntity || !pEntity->IsPlayer() )
  1090. continue;
  1091. if ( pEntity->m_takedamage != DAMAGE_NO )
  1092. {
  1093. // radius damage can only be blocked by the world
  1094. vecSpot = pEntity->BodyTarget( vecSrc );
  1095. // the explosion can 'see' this entity, so hurt them!
  1096. vecToTarget = ( vecSpot - vecSrc );
  1097. float flDist = vecToTarget.Length();
  1098. // make sure they are inside the radius
  1099. if ( flDist > flRadius )
  1100. continue;
  1101. // decrease damage for an ent that's farther from the bomb.
  1102. flAdjustedDamage = flDist * falloff;
  1103. flAdjustedDamage = info.GetDamage() - flAdjustedDamage;
  1104. if ( flAdjustedDamage > 0 )
  1105. {
  1106. CTakeDamageInfo adjustedInfo = info;
  1107. adjustedInfo.SetDamage( flAdjustedDamage );
  1108. pEntity->TakeDamage( adjustedInfo );
  1109. }
  1110. }
  1111. }
  1112. }
  1113. void CDODGameRules::Think()
  1114. {
  1115. if ( g_fGameOver ) // someone else quit the game already
  1116. {
  1117. // check to see if we should change levels now
  1118. if ( m_flIntermissionEndTime < gpGlobals->curtime )
  1119. {
  1120. ChangeLevel(); // intermission is over
  1121. }
  1122. return;
  1123. }
  1124. State_Think();
  1125. if ( gpGlobals->curtime > m_flNextPeriodicThink )
  1126. {
  1127. if ( CheckTimeLimit() )
  1128. return;
  1129. if ( CheckWinLimit() )
  1130. return;
  1131. CheckRestartRound();
  1132. CheckWarmup();
  1133. CheckPlayerPositions();
  1134. m_flNextPeriodicThink = gpGlobals->curtime + 1.0;
  1135. }
  1136. CGameRules::Think();
  1137. }
  1138. void CDODGameRules::GoToIntermission( void )
  1139. {
  1140. BaseClass::GoToIntermission();
  1141. // set all players to FL_FROZEN
  1142. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  1143. {
  1144. CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  1145. if ( pPlayer )
  1146. {
  1147. pPlayer->AddFlag( FL_FROZEN );
  1148. pPlayer->StatEvent_UploadStats();
  1149. }
  1150. }
  1151. // Print out map stats to a text file
  1152. //WriteStatsFile( "stats.xml" );
  1153. State_Enter( STATE_GAME_OVER );
  1154. }
  1155. void CDODGameRules::SetInWarmup( bool bWarmup )
  1156. {
  1157. if( m_bInWarmup == bWarmup )
  1158. return;
  1159. m_bInWarmup = bWarmup;
  1160. if( m_bInWarmup )
  1161. {
  1162. m_flWarmupTimeEnds = gpGlobals->curtime + mp_warmup_time.GetFloat();
  1163. DevMsg( "Warmup_Begin\n" );
  1164. IGameEvent *event = gameeventmanager->CreateEvent( "dod_warmup_begins" );
  1165. if ( event )
  1166. gameeventmanager->FireEvent( event );
  1167. }
  1168. else
  1169. {
  1170. m_flWarmupTimeEnds = -1;
  1171. DevMsg( "Warmup_Ends\n" );
  1172. IGameEvent *event = gameeventmanager->CreateEvent( "dod_warmup_ends" );
  1173. if ( event )
  1174. gameeventmanager->FireEvent( event );
  1175. }
  1176. }
  1177. void CDODGameRules::CheckWarmup( void )
  1178. {
  1179. if( mp_restartwarmup.GetBool() )
  1180. {
  1181. if( m_bInWarmup )
  1182. {
  1183. m_flWarmupTimeEnds = gpGlobals->curtime + mp_warmup_time.GetFloat();
  1184. }
  1185. else
  1186. DODGameRules()->SetInWarmup( true );
  1187. mp_restartwarmup.SetValue( 0 );
  1188. }
  1189. if( mp_cancelwarmup.GetBool() )
  1190. {
  1191. DODGameRules()->SetInWarmup( false );
  1192. mp_cancelwarmup.SetValue( 0 );
  1193. }
  1194. if( m_bInWarmup )
  1195. {
  1196. // only exit the warmup if the time is up, and we are not in a round
  1197. // restart countdown already, and we are not waiting for a ready restart
  1198. if( gpGlobals->curtime > m_flWarmupTimeEnds && m_flRestartRoundTime < 0 && !m_bAwaitingReadyRestart )
  1199. {
  1200. // no need to end the warmup, the restart will end it automatically
  1201. //SetInWarmup( false );
  1202. m_flRestartRoundTime = gpGlobals->curtime; // reset asap
  1203. }
  1204. }
  1205. }
  1206. void CDODGameRules::CheckRestartRound( void )
  1207. {
  1208. if( mp_clan_readyrestart.GetBool() )
  1209. {
  1210. m_bAwaitingReadyRestart = true;
  1211. m_bHeardAlliesReady = false;
  1212. m_bHeardAxisReady = false;
  1213. const char *pszReadyString = mp_clan_ready_signal.GetString();
  1214. UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#clan_ready_rules", pszReadyString );
  1215. UTIL_ClientPrintAll( HUD_PRINTTALK, "#clan_ready_rules", pszReadyString );
  1216. // Don't let them put anything malicious in there
  1217. if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 )
  1218. {
  1219. pszReadyString = "ready";
  1220. }
  1221. IGameEvent *event = gameeventmanager->CreateEvent( "dod_ready_restart" );
  1222. if ( event )
  1223. gameeventmanager->FireEvent( event );
  1224. mp_clan_readyrestart.SetValue( 0 );
  1225. // cancel any restart round in progress
  1226. m_flRestartRoundTime = -1;
  1227. }
  1228. // Restart the game if specified by the server
  1229. int iRestartDelay = mp_clan_restartround.GetInt();
  1230. if ( iRestartDelay > 0 )
  1231. {
  1232. if ( iRestartDelay > 60 )
  1233. iRestartDelay = 60;
  1234. m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay;
  1235. IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_restart_seconds" );
  1236. if ( event )
  1237. {
  1238. event->SetInt( "seconds", iRestartDelay );
  1239. gameeventmanager->FireEvent( event );
  1240. }
  1241. mp_clan_restartround.SetValue( 0 );
  1242. // cancel any ready restart in progress
  1243. m_bAwaitingReadyRestart = false;
  1244. }
  1245. }
  1246. bool CDODGameRules::CheckTimeLimit()
  1247. {
  1248. if ( IsGameUnderTimeLimit() )
  1249. {
  1250. if( GetTimeLeft() <= 0 )
  1251. {
  1252. IGameEvent *event = gameeventmanager->CreateEvent( "dod_game_over" );
  1253. if ( event )
  1254. {
  1255. event->SetString( "reason", "Reached Time Limit" );
  1256. gameeventmanager->FireEvent( event );
  1257. }
  1258. SendTeamScoresEvent();
  1259. GoToIntermission();
  1260. return true;
  1261. }
  1262. }
  1263. return false;
  1264. }
  1265. bool CDODGameRules::CheckWinLimit()
  1266. {
  1267. // has one team won the specified number of rounds?
  1268. int iWinLimit = mp_winlimit.GetInt();
  1269. if ( iWinLimit > 0 )
  1270. {
  1271. CDODTeam *pAllies = GetGlobalDODTeam(TEAM_ALLIES);
  1272. CDODTeam *pAxis = GetGlobalDODTeam(TEAM_AXIS);
  1273. bool bAlliesWin = pAllies->GetRoundsWon() >= iWinLimit;
  1274. bool bAxisWin = pAxis->GetRoundsWon() >= iWinLimit;
  1275. if ( bAlliesWin || bAxisWin )
  1276. {
  1277. IGameEvent *event = gameeventmanager->CreateEvent( "dod_game_over" );
  1278. if ( event )
  1279. {
  1280. event->SetString( "reason", "Reached Round Win Limit" );
  1281. gameeventmanager->FireEvent( event );
  1282. }
  1283. GoToIntermission();
  1284. return true;
  1285. }
  1286. }
  1287. return false;
  1288. }
  1289. void CDODGameRules::CheckPlayerPositions()
  1290. {
  1291. int i;
  1292. bool bUpdatePlayer[MAX_PLAYERS];
  1293. Q_memset( bUpdatePlayer, 0, sizeof(bUpdatePlayer) );
  1294. // check all players
  1295. for ( i=1; i<=gpGlobals->maxClients; i++ )
  1296. {
  1297. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  1298. if ( !pPlayer )
  1299. continue;
  1300. Vector origin = pPlayer->GetAbsOrigin();
  1301. Vector2D pos( (int)(origin.x/4), (int)(origin.y/4) );
  1302. if ( pos == m_vecPlayerPositions[i-1] )
  1303. continue; // player didn't move enough
  1304. m_vecPlayerPositions[i-1] = pos;
  1305. bUpdatePlayer[i-1] = true; // player position changed since last time
  1306. }
  1307. // ok, now send updates to all clients
  1308. CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits;
  1309. for ( i=1; i<=gpGlobals->maxClients; i++ )
  1310. {
  1311. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  1312. if ( !pPlayer )
  1313. continue;
  1314. if ( !pPlayer->IsConnected() )
  1315. continue;
  1316. CSingleUserRecipientFilter filter(pPlayer);
  1317. UserMessageBegin( filter, "UpdateRadar" );
  1318. playerbits.ClearAll();
  1319. // see what other players are in it's PVS, don't update them
  1320. engine->Message_DetermineMulticastRecipients( false, pPlayer->EyePosition(), playerbits );
  1321. for ( int i=0; i < gpGlobals->maxClients; i++ )
  1322. {
  1323. if ( playerbits.Get(i) )
  1324. continue; // this player is in his PVS, don't update radar pos
  1325. if ( !bUpdatePlayer[i] )
  1326. continue;
  1327. CBasePlayer *pOtherPlayer = UTIL_PlayerByIndex( i+1 );
  1328. if ( !pOtherPlayer )
  1329. continue; // nothing there
  1330. if ( pOtherPlayer == pPlayer )
  1331. continue; // dont update himself
  1332. if ( !pOtherPlayer->IsAlive() || pOtherPlayer->IsObserver() || !pOtherPlayer->IsConnected() )
  1333. continue; // don't update spectators or dead players
  1334. if ( pPlayer->GetTeamNumber() > TEAM_SPECTATOR )
  1335. {
  1336. // update only team mates if not a pure spectator
  1337. if ( pPlayer->GetTeamNumber() != pOtherPlayer->GetTeamNumber() )
  1338. continue;
  1339. }
  1340. WRITE_BYTE( i+1 ); // player entity index
  1341. WRITE_SBITLONG( m_vecPlayerPositions[i].x, COORD_INTEGER_BITS-1 );
  1342. WRITE_SBITLONG( m_vecPlayerPositions[i].y, COORD_INTEGER_BITS-1 );
  1343. WRITE_SBITLONG( AngleNormalize( pOtherPlayer->GetAbsAngles().y ), 9 );
  1344. }
  1345. WRITE_BYTE( 0 ); // end marker
  1346. MessageEnd(); // send message
  1347. }
  1348. }
  1349. Vector DropToGround(
  1350. CBaseEntity *pMainEnt,
  1351. const Vector &vPos,
  1352. const Vector &vMins,
  1353. const Vector &vMaxs )
  1354. {
  1355. trace_t trace;
  1356. UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace );
  1357. return trace.endpos;
  1358. }
  1359. void TestSpawnPointType( const char *pEntClassName )
  1360. {
  1361. // Find the next spawn spot.
  1362. CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, pEntClassName );
  1363. while( pSpot )
  1364. {
  1365. // check if pSpot is valid
  1366. if( g_pGameRules->IsSpawnPointValid( pSpot, NULL ) )
  1367. {
  1368. // the successful spawn point's location
  1369. NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 100, 60 );
  1370. // drop down to ground
  1371. Vector GroundPos = DropToGround( NULL, pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX );
  1372. // the location the player will spawn at
  1373. NDebugOverlay::Box( GroundPos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 0, 255, 100, 60 );
  1374. // draw the spawn angles
  1375. QAngle spotAngles = pSpot->GetLocalAngles();
  1376. Vector vecForward;
  1377. AngleVectors( spotAngles, &vecForward );
  1378. NDebugOverlay::HorzArrow( pSpot->GetAbsOrigin(), pSpot->GetAbsOrigin() + vecForward * 32, 10, 255, 0, 0, 255, true, 60 );
  1379. }
  1380. else
  1381. {
  1382. // failed spawn point location
  1383. NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 100, 60 );
  1384. }
  1385. // increment pSpot
  1386. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
  1387. }
  1388. }
  1389. void TestSpawns()
  1390. {
  1391. TestSpawnPointType( "info_player_allies" );
  1392. TestSpawnPointType( "info_player_axis" );
  1393. }
  1394. ConCommand cc_TestSpawns( "map_showspawnpoints", TestSpawns, "Dev - test the spawn points, draws for 60 seconds", FCVAR_CHEAT );
  1395. CBaseEntity *CDODGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer )
  1396. {
  1397. // get valid spawn point
  1398. CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint();
  1399. // drop down to ground
  1400. Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX );
  1401. // Move the player to the place it said.
  1402. pPlayer->Teleport( &GroundPos, &pSpawnSpot->GetLocalAngles(), &vec3_origin );
  1403. pPlayer->m_Local.m_vecPunchAngle = vec3_angle;
  1404. return pSpawnSpot;
  1405. }
  1406. // checks if the spot is clear of players
  1407. bool CDODGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer )
  1408. {
  1409. if ( !pSpot->IsTriggered( pPlayer ) )
  1410. {
  1411. return false;
  1412. }
  1413. // Check if it is disabled by Enable/Disable
  1414. CSpawnPoint *pSpawnPoint = dynamic_cast< CSpawnPoint * >( pSpot );
  1415. if ( pSpawnPoint )
  1416. {
  1417. if ( pSpawnPoint->IsDisabled() )
  1418. {
  1419. return false;
  1420. }
  1421. }
  1422. Vector mins = GetViewVectors()->m_vHullMin;
  1423. Vector maxs = GetViewVectors()->m_vHullMax;
  1424. Vector vTestMins = pSpot->GetAbsOrigin() + mins;
  1425. Vector vTestMaxs = pSpot->GetAbsOrigin() + maxs;
  1426. // First test the starting origin.
  1427. return UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs );
  1428. }
  1429. void CDODGameRules::PlayerSpawn( CBasePlayer *p )
  1430. {
  1431. CDODPlayer *pPlayer = ToDODPlayer( p );
  1432. int team = pPlayer->GetTeamNumber();
  1433. if( team == TEAM_ALLIES || team == TEAM_AXIS )
  1434. {
  1435. int iPreviousPlayerClass = pPlayer->m_Shared.PlayerClass();
  1436. if( pPlayer->m_Shared.DesiredPlayerClass() == PLAYERCLASS_RANDOM )
  1437. {
  1438. ChooseRandomClass( pPlayer );
  1439. ClientPrint( pPlayer, HUD_PRINTTALK, "#game_now_as", GetPlayerClassName( pPlayer->m_Shared.PlayerClass(), team ) );
  1440. }
  1441. else
  1442. {
  1443. pPlayer->m_Shared.SetPlayerClass( pPlayer->m_Shared.DesiredPlayerClass() );
  1444. }
  1445. int playerclass = pPlayer->m_Shared.PlayerClass();
  1446. if ( playerclass != iPreviousPlayerClass )
  1447. {
  1448. // spawning as a new class, flush stats
  1449. pPlayer->StatEvent_UploadStats();
  1450. }
  1451. if( playerclass != PLAYERCLASS_UNDEFINED )
  1452. {
  1453. //Assert( PLAYERCLASS_UNDEFINED < playerclass && playerclass < NUM_PLAYERCLASSES );
  1454. int i;
  1455. CDODTeam *pTeam = GetGlobalDODTeam( team );
  1456. const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( playerclass );
  1457. Assert( pClassInfo.m_iTeam == team );
  1458. pPlayer->SetModel( pClassInfo.m_szPlayerModel );
  1459. pPlayer->SetHitboxSet( 0 );
  1460. char buf[64];
  1461. int bufsize = sizeof(buf);
  1462. //Give weapons
  1463. // Primary weapon
  1464. Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iPrimaryWeapon) );
  1465. CBaseEntity *pPrimaryWpn = pPlayer->GiveNamedItem( buf );
  1466. Assert( pPrimaryWpn );
  1467. // Secondary weapon
  1468. CBaseEntity *pSecondaryWpn = NULL;
  1469. if ( pClassInfo.m_iSecondaryWeapon != WEAPON_NONE )
  1470. {
  1471. Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iSecondaryWeapon) );
  1472. pSecondaryWpn = pPlayer->GiveNamedItem( buf );
  1473. }
  1474. // Melee weapon
  1475. if ( pClassInfo.m_iMeleeWeapon )
  1476. {
  1477. Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iMeleeWeapon) );
  1478. pPlayer->GiveNamedItem( buf );
  1479. }
  1480. CWeaponDODBase *pWpn = NULL;
  1481. // Primary Ammo
  1482. pWpn = dynamic_cast<CWeaponDODBase *>(pPrimaryWpn);
  1483. if( pWpn )
  1484. {
  1485. int iNumClip = pWpn->GetDODWpnData().m_iDefaultAmmoClips - 1; //account for one clip in the gun
  1486. int iClipSize = pWpn->GetDODWpnData().iMaxClip1;
  1487. pPlayer->GiveAmmo( iNumClip * iClipSize, pWpn->GetDODWpnData().szAmmo1 );
  1488. }
  1489. // Secondary Ammo
  1490. if ( pSecondaryWpn )
  1491. {
  1492. pWpn = dynamic_cast<CWeaponDODBase *>(pSecondaryWpn);
  1493. if( pWpn )
  1494. {
  1495. int iNumClip = pWpn->GetDODWpnData().m_iDefaultAmmoClips - 1; //account for one clip in the gun
  1496. int iClipSize = pWpn->GetDODWpnData().iMaxClip1;
  1497. pPlayer->GiveAmmo( iNumClip * iClipSize, pWpn->GetDODWpnData().szAmmo1 );
  1498. }
  1499. }
  1500. // Grenade Type 1
  1501. if ( pClassInfo.m_iGrenType1 != WEAPON_NONE )
  1502. {
  1503. Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iGrenType1) );
  1504. for ( i=0;i<pClassInfo.m_iNumGrensType1;i++ )
  1505. {
  1506. pPlayer->GiveNamedItem( buf );
  1507. }
  1508. }
  1509. // Grenade Type 2
  1510. if ( pClassInfo.m_iGrenType2 != WEAPON_NONE )
  1511. {
  1512. Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iGrenType2) );
  1513. for ( i=0;i<pClassInfo.m_iNumGrensType2;i++ )
  1514. {
  1515. pPlayer->GiveNamedItem( buf );
  1516. }
  1517. }
  1518. pPlayer->Weapon_Switch( (CBaseCombatWeapon *)pPrimaryWpn );
  1519. // you get a helmet
  1520. pPlayer->SetBodygroup( BODYGROUP_HELMET, pClassInfo.m_iHelmetGroup );
  1521. // no jumpgear
  1522. pPlayer->SetBodygroup( BODYGROUP_JUMPGEAR, BODYGROUP_JUMPGEAR_OFF );
  1523. pPlayer->SetMaxSpeed( 600 );
  1524. Assert( playerclass >= 0 && playerclass <= 5 );
  1525. if ( playerclass >= 0 && playerclass <= 5 )
  1526. {
  1527. if ( team == TEAM_ALLIES )
  1528. m_iStatsSpawnsPerClass_Allies[playerclass]++;
  1529. else if ( team == TEAM_AXIS )
  1530. m_iStatsSpawnsPerClass_Axis[playerclass]++;
  1531. }
  1532. }
  1533. else
  1534. {
  1535. Assert( !"Player spawning with PLAYERCLASS_UNDEFINED" );
  1536. pPlayer->SetModel( NULL );
  1537. }
  1538. }
  1539. }
  1540. const char *CDODGameRules::GetPlayerClassName( int cls, int team )
  1541. {
  1542. CDODTeam *pTeam = GetGlobalDODTeam( team );
  1543. if( cls == PLAYERCLASS_RANDOM )
  1544. {
  1545. return "#class_random";
  1546. }
  1547. if( cls < 0 || cls >= pTeam->GetNumPlayerClasses() )
  1548. {
  1549. Assert( false );
  1550. return NULL;
  1551. }
  1552. const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( cls );
  1553. return pClassInfo.m_szPrintName;
  1554. }
  1555. void CDODGameRules::ChooseRandomClass( CDODPlayer *pPlayer )
  1556. {
  1557. int i;
  1558. int numChoices = 0;
  1559. int choices[16];
  1560. int firstclass = 0;
  1561. CDODTeam *pTeam = GetGlobalDODTeam( pPlayer->GetTeamNumber() );
  1562. int lastclass = pTeam->GetNumPlayerClasses();
  1563. int previousClass = pPlayer->m_Shared.PlayerClass();
  1564. // Compile a list of the classes that aren't full
  1565. for( i=firstclass;i<lastclass;i++ )
  1566. {
  1567. // don't join the same class twice in a row
  1568. if ( i == previousClass )
  1569. continue;
  1570. if( CanPlayerJoinClass( pPlayer, i ) )
  1571. {
  1572. choices[numChoices] = i;
  1573. numChoices++;
  1574. }
  1575. }
  1576. // If ALL the classes are full
  1577. if( numChoices == 0 )
  1578. {
  1579. Msg( "Random class found that all classes were full - ignoring class limits for this spawn\n" );
  1580. pPlayer->m_Shared.SetPlayerClass( random->RandomFloat( firstclass, lastclass ) );
  1581. }
  1582. else
  1583. {
  1584. // Choose a slot randomly
  1585. i = random->RandomInt( 0, numChoices-1 );
  1586. // We are now the class that was in that slot
  1587. pPlayer->m_Shared.SetPlayerClass( choices[i] );
  1588. }
  1589. }
  1590. //-----------------------------------------------------------------------------
  1591. // Purpose: This function can be used to find a valid placement location for an entity.
  1592. // Given an origin to start looking from and a minimum radius to place the entity at,
  1593. // it will sweep out a circle around vOrigin and try to find a valid spot (on the ground)
  1594. // where mins and maxs will fit.
  1595. // Input : *pMainEnt - Entity to place
  1596. // &vOrigin - Point to search around
  1597. // fRadius - Radius to search within
  1598. // nTries - Number of tries to attempt
  1599. // &mins - mins of the Entity
  1600. // &maxs - maxs of the Entity
  1601. // &outPos - Return point
  1602. // Output : Returns true and fills in outPos if it found a spot.
  1603. //-----------------------------------------------------------------------------
  1604. bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround )
  1605. {
  1606. // This function moves the box out in each dimension in each step trying to find empty space like this:
  1607. //
  1608. // X
  1609. // X X
  1610. // Step 1: X Step 2: XXX Step 3: XXXXX
  1611. // X X
  1612. // X
  1613. //
  1614. Vector mins, maxs;
  1615. pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
  1616. mins -= pMainEnt->GetAbsOrigin();
  1617. maxs -= pMainEnt->GetAbsOrigin();
  1618. // Put some padding on their bbox.
  1619. Vector vTestMins = mins;
  1620. Vector vTestMaxs = maxs;
  1621. // First test the starting origin.
  1622. if ( UTIL_IsSpaceEmpty( pMainEnt, vOrigin + vTestMins, vOrigin + vTestMaxs ) )
  1623. {
  1624. if ( bDropToGround )
  1625. {
  1626. outPos = DropToGround( pMainEnt, vOrigin, vTestMins, vTestMaxs );
  1627. }
  1628. else
  1629. {
  1630. outPos = vOrigin;
  1631. }
  1632. return true;
  1633. }
  1634. Vector vDims = vTestMaxs - vTestMins;
  1635. // Keep branching out until we get too far.
  1636. int iCurIteration = 0;
  1637. int nMaxIterations = 15;
  1638. int offset = 0;
  1639. do
  1640. {
  1641. for ( int iDim=0; iDim < 2; iDim++ )
  1642. {
  1643. float flCurOffset = offset * vDims[iDim];
  1644. for ( int iSign=0; iSign < 2; iSign++ )
  1645. {
  1646. Vector vBase = vOrigin;
  1647. vBase[iDim] += (iSign*2-1) * flCurOffset;
  1648. if ( UTIL_IsSpaceEmpty( pMainEnt, vBase + vTestMins, vBase + vTestMaxs ) )
  1649. {
  1650. // Ensure that there is a clear line of sight from the spawnpoint entity to the actual spawn point.
  1651. // (Useful for keeping things from spawning behind walls near a spawn point)
  1652. trace_t tr;
  1653. UTIL_TraceLine( vOrigin, vBase, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &tr );
  1654. if ( tr.fraction != 1.0 )
  1655. {
  1656. continue;
  1657. }
  1658. if ( bDropToGround )
  1659. outPos = DropToGround( pMainEnt, vBase, vTestMins, vTestMaxs );
  1660. else
  1661. outPos = vBase;
  1662. return true;
  1663. }
  1664. }
  1665. }
  1666. ++offset;
  1667. } while ( iCurIteration++ < nMaxIterations );
  1668. // Warning( "EntityPlacementTest for ent %d:%s failed!\n", pMainEnt->entindex(), pMainEnt->GetClassname() );
  1669. return false;
  1670. }
  1671. bool CDODGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon )
  1672. {
  1673. //only allow one primary, one secondary and one melee
  1674. CWeaponDODBase *pWpn = (CWeaponDODBase *)pWeapon;
  1675. if( pWpn )
  1676. {
  1677. int type = pWpn->GetDODWpnData().m_WeaponType;
  1678. switch( type )
  1679. {
  1680. case WPN_TYPE_MELEE:
  1681. {
  1682. #ifdef DEBUG
  1683. CWeaponDODBase *pMeleeWeapon = (CWeaponDODBase *)pPlayer->Weapon_GetSlot( WPN_SLOT_MELEE );
  1684. bool bHasMelee = ( pMeleeWeapon != NULL );
  1685. if( bHasMelee )
  1686. {
  1687. Assert( !"Why are we trying to add another melee?" );
  1688. return false;
  1689. }
  1690. #endif
  1691. }
  1692. break;
  1693. case WPN_TYPE_PISTOL:
  1694. case WPN_TYPE_SIDEARM:
  1695. {
  1696. #ifdef DEBUG
  1697. CWeaponDODBase *pSecondaryWeapon = (CWeaponDODBase *)pPlayer->Weapon_GetSlot( WPN_SLOT_SECONDARY );
  1698. bool bHasPistol = ( pSecondaryWeapon != NULL );
  1699. if( bHasPistol )
  1700. {
  1701. Assert( !"Why are we trying to add another pistol?" );
  1702. return false;
  1703. }
  1704. #endif
  1705. }
  1706. break;
  1707. case WPN_TYPE_CAMERA:
  1708. return true;
  1709. case WPN_TYPE_RIFLE:
  1710. case WPN_TYPE_SNIPER:
  1711. case WPN_TYPE_SUBMG:
  1712. case WPN_TYPE_MG:
  1713. case WPN_TYPE_BAZOOKA:
  1714. {
  1715. //Don't pick up dropped weapons if we have one already
  1716. CWeaponDODBase *pPrimaryWeapon = (CWeaponDODBase *)pPlayer->Weapon_GetSlot( WPN_SLOT_PRIMARY );
  1717. bool bHasPrimary = ( pPrimaryWeapon != NULL );
  1718. if( bHasPrimary )
  1719. return false;
  1720. }
  1721. break;
  1722. default:
  1723. break;
  1724. }
  1725. }
  1726. return BaseClass::CanHavePlayerItem( pPlayer, pWeapon );
  1727. }
  1728. void CDODGameRules::ResetMapTime( void )
  1729. {
  1730. m_flMapResetTime = gpGlobals->curtime;
  1731. // send an event with the time remaining until map change
  1732. IGameEvent *event = gameeventmanager->CreateEvent( "dod_map_time_remaining" );
  1733. if ( event )
  1734. {
  1735. event->SetInt( "seconds", GetTimeLeft() );
  1736. gameeventmanager->FireEvent( event );
  1737. }
  1738. }
  1739. #endif
  1740. bool CDODGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 )
  1741. {
  1742. if ( collisionGroup0 > collisionGroup1 )
  1743. {
  1744. // swap so that lowest is always first
  1745. V_swap(collisionGroup0,collisionGroup1);
  1746. }
  1747. //Don't stand on COLLISION_GROUP_WEAPONs
  1748. if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT &&
  1749. collisionGroup1 == COLLISION_GROUP_WEAPON )
  1750. {
  1751. return false;
  1752. }
  1753. // TE shells don't collide with the player
  1754. if ( collisionGroup0 == COLLISION_GROUP_PLAYER &&
  1755. collisionGroup1 == DOD_COLLISIONGROUP_SHELLS )
  1756. {
  1757. return false;
  1758. }
  1759. // blocker walls only collide with players
  1760. if ( collisionGroup1 == DOD_COLLISIONGROUP_BLOCKERWALL )
  1761. return ( collisionGroup0 == COLLISION_GROUP_PLAYER ) || ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT );
  1762. return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 );
  1763. }
  1764. int CDODGameRules::GetSubTeam( int team )
  1765. {
  1766. return SUBTEAM_NORMAL;
  1767. }
  1768. bool CDODGameRules::IsGameUnderTimeLimit( void )
  1769. {
  1770. return ( mp_timelimit.GetInt() > 0 );
  1771. }
  1772. int CDODGameRules::GetTimeLeft( void )
  1773. {
  1774. float flTimeLimit = mp_timelimit.GetInt() * 60;
  1775. Assert( flTimeLimit > 0 && "Should not call this function when !IsGameUnderTimeLimit" );
  1776. float flMapChangeTime = m_flMapResetTime + flTimeLimit;
  1777. #ifndef CLIENT_DLL
  1778. // If the round timer is longer, let the round complete
  1779. if ( m_bUsingTimer && m_pRoundTimer )
  1780. {
  1781. float flTimerSeconds = m_pRoundTimer->GetTimeRemaining();
  1782. float flMapChangeSeconds = flMapChangeTime - gpGlobals->curtime;
  1783. // if the map timer is less than the round timer
  1784. // AND
  1785. // the round timer is less than 2 minutes
  1786. // If the map time for any reason goes beyond the end of the round, remove the flag
  1787. if ( flMapChangeSeconds > flTimerSeconds )
  1788. {
  1789. m_bChangeLevelOnRoundEnd = false;
  1790. }
  1791. else if ( m_bChangeLevelOnRoundEnd || flTimerSeconds < 120 )
  1792. {
  1793. // once this happens once in a round, use this until the round ends
  1794. // or else the round will end when a team captures an objective and adds time to above 120
  1795. m_bChangeLevelOnRoundEnd = true;
  1796. return (int)( flTimerSeconds );
  1797. }
  1798. }
  1799. #endif
  1800. return ( (int)(flMapChangeTime - gpGlobals->curtime) );
  1801. }
  1802. int CDODGameRules::GetReinforcementTimerSeconds( int team, float flSpawnEligibleTime )
  1803. {
  1804. // find the first wave that this player can fit in
  1805. float flWaveTime = -1;
  1806. switch( team )
  1807. {
  1808. case TEAM_ALLIES:
  1809. {
  1810. int i = m_iAlliesRespawnHead;
  1811. while( i != m_iAlliesRespawnTail )
  1812. {
  1813. if ( flSpawnEligibleTime < m_AlliesRespawnQueue[i] )
  1814. {
  1815. flWaveTime = m_AlliesRespawnQueue[i];
  1816. break;
  1817. }
  1818. i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE;
  1819. }
  1820. }
  1821. break;
  1822. case TEAM_AXIS:
  1823. {
  1824. int i = m_iAxisRespawnHead;
  1825. while( i != m_iAxisRespawnTail )
  1826. {
  1827. if ( flSpawnEligibleTime < m_AxisRespawnQueue[i] )
  1828. {
  1829. flWaveTime = m_AxisRespawnQueue[i];
  1830. break;
  1831. }
  1832. i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE;
  1833. }
  1834. }
  1835. break;
  1836. default:
  1837. return -1;
  1838. }
  1839. return MAX( 0, (int)( flWaveTime - gpGlobals->curtime ) );
  1840. }
  1841. const CViewVectors* CDODGameRules::GetViewVectors() const
  1842. {
  1843. return &g_DODViewVectors;
  1844. }
  1845. const CDODViewVectors *CDODGameRules::GetDODViewVectors() const
  1846. {
  1847. return &g_DODViewVectors;
  1848. }
  1849. #ifndef CLIENT_DLL
  1850. extern ConVar dod_bonusround;
  1851. bool CDODGameRules::IsFriendlyFireOn( void )
  1852. {
  1853. // Never friendly fire in bonus round
  1854. if ( IsInBonusRound() )
  1855. {
  1856. return false;
  1857. }
  1858. return friendlyfire.GetBool();
  1859. }
  1860. bool CDODGameRules::IsInBonusRound( void )
  1861. {
  1862. return ( dod_bonusround.GetBool() == true && ( State_Get() == STATE_ALLIES_WIN || State_Get() == STATE_AXIS_WIN ) );
  1863. }
  1864. ConVar dod_showroundtransitions( "dod_showroundtransitions", "0", 0, "Show gamestate round transitions" );
  1865. void CDODGameRules::State_Transition( DODRoundState newState )
  1866. {
  1867. State_Leave();
  1868. State_Enter( newState );
  1869. }
  1870. void CDODGameRules::State_Enter( DODRoundState newState )
  1871. {
  1872. m_iRoundState = newState;
  1873. m_pCurStateInfo = State_LookupInfo( newState );
  1874. if ( dod_showroundtransitions.GetInt() > 0 )
  1875. {
  1876. if ( m_pCurStateInfo )
  1877. Msg( "DODRoundState: entering '%s'\n", m_pCurStateInfo->m_pStateName );
  1878. else
  1879. Msg( "DODRoundState: entering #%d\n", newState );
  1880. }
  1881. // Initialize the new state.
  1882. if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
  1883. (this->*m_pCurStateInfo->pfnEnterState)();
  1884. }
  1885. void CDODGameRules::State_Leave()
  1886. {
  1887. if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
  1888. {
  1889. (this->*m_pCurStateInfo->pfnLeaveState)();
  1890. }
  1891. }
  1892. void CDODGameRules::State_Think()
  1893. {
  1894. if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink )
  1895. {
  1896. (this->*m_pCurStateInfo->pfnThink)();
  1897. }
  1898. }
  1899. CDODRoundStateInfo* CDODGameRules::State_LookupInfo( DODRoundState state )
  1900. {
  1901. static CDODRoundStateInfo playerStateInfos[] =
  1902. {
  1903. { STATE_INIT, "STATE_INIT", &CDODGameRules::State_Enter_INIT, NULL, &CDODGameRules::State_Think_INIT },
  1904. { STATE_PREGAME, "STATE_PREGAME", &CDODGameRules::State_Enter_PREGAME, NULL, &CDODGameRules::State_Think_PREGAME },
  1905. { STATE_STARTGAME, "STATE_STARTGAME", &CDODGameRules::State_Enter_STARTGAME, NULL, &CDODGameRules::State_Think_STARTGAME },
  1906. { STATE_PREROUND, "STATE_PREROUND", &CDODGameRules::State_Enter_PREROUND, NULL, &CDODGameRules::State_Think_PREROUND },
  1907. { STATE_RND_RUNNING,"STATE_RND_RUNNING",&CDODGameRules::State_Enter_RND_RUNNING, NULL, &CDODGameRules::State_Think_RND_RUNNING },
  1908. { STATE_ALLIES_WIN, "STATE_ALLIES_WIN", &CDODGameRules::State_Enter_ALLIES_WIN, NULL, &CDODGameRules::State_Think_ALLIES_WIN },
  1909. { STATE_AXIS_WIN, "STATE_AXIS_WIN", &CDODGameRules::State_Enter_AXIS_WIN, NULL, &CDODGameRules::State_Think_AXIS_WIN },
  1910. { STATE_RESTART, "STATE_RESTART", &CDODGameRules::State_Enter_RESTART, NULL, &CDODGameRules::State_Think_RESTART },
  1911. { STATE_GAME_OVER, "STATE_GAME_OVER", NULL, NULL, NULL },
  1912. };
  1913. for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
  1914. {
  1915. if ( playerStateInfos[i].m_iRoundState == state )
  1916. return &playerStateInfos[i];
  1917. }
  1918. return NULL;
  1919. }
  1920. extern ConVar sv_stopspeed;
  1921. extern ConVar sv_friction;
  1922. void CDODGameRules::State_Enter_INIT( void )
  1923. {
  1924. InitTeams();
  1925. sv_stopspeed.SetValue( 50.0f );
  1926. sv_friction.SetValue( 8.0f );
  1927. ResetMapTime();
  1928. }
  1929. void CDODGameRules::State_Think_INIT( void )
  1930. {
  1931. State_Transition( STATE_PREGAME );
  1932. }
  1933. void CDODGameRules::InitTeams( void )
  1934. {
  1935. Assert( g_Teams.Count() == 0 );
  1936. g_Teams.Purge(); // just in case
  1937. // Create the team managers
  1938. int i;
  1939. for ( i = 0; i < 2; i++ ) // Unassigned and Spectators
  1940. {
  1941. CTeam *pTeam = static_cast<CTeam*>(CreateEntityByName( "dod_team_manager" ));
  1942. pTeam->Init( sTeamNames[i], i );
  1943. g_Teams.AddToTail( pTeam );
  1944. }
  1945. // clear the player class data
  1946. ResetFilePlayerClassInfoDatabase();
  1947. CTeam *pAllies = static_cast<CTeam*>(CreateEntityByName( "dod_team_allies" ));
  1948. Assert( pAllies );
  1949. pAllies->Init( sTeamNames[TEAM_ALLIES], TEAM_ALLIES );
  1950. g_Teams.AddToTail( pAllies );
  1951. CTeam *pAxis = static_cast<CTeam*>(CreateEntityByName( "dod_team_axis" ));
  1952. Assert( pAxis );
  1953. pAxis->Init( sTeamNames[TEAM_AXIS], TEAM_AXIS );
  1954. g_Teams.AddToTail( pAxis );
  1955. }
  1956. // dod_control_point_master can take inputs to add time to the round timer
  1957. void CDODGameRules::AddTimerSeconds( int iSecondsToAdd )
  1958. {
  1959. if( m_bUsingTimer && m_pRoundTimer )
  1960. {
  1961. m_pRoundTimer->AddTimerSeconds( iSecondsToAdd );
  1962. float flTimerSeconds = m_pRoundTimer->GetTimeRemaining();
  1963. m_bPlayTimerWarning_1Minute = ( flTimerSeconds > 60 );
  1964. m_bPlayTimerWarning_2Minute = ( flTimerSeconds > 120 );
  1965. IGameEvent *event = gameeventmanager->CreateEvent( "dod_timer_time_added" );
  1966. if ( event )
  1967. {
  1968. event->SetInt( "seconds_added", iSecondsToAdd );
  1969. gameeventmanager->FireEvent( event );
  1970. }
  1971. }
  1972. }
  1973. int CDODGameRules::GetTimerSeconds( void )
  1974. {
  1975. if( m_bUsingTimer && m_pRoundTimer )
  1976. {
  1977. return m_pRoundTimer->GetTimeRemaining();
  1978. }
  1979. else
  1980. {
  1981. return 0;
  1982. }
  1983. }
  1984. // PREGAME - the server is idle and waiting for enough
  1985. // players to start up again. When we find an active player
  1986. // go to STATE_STARTGAME
  1987. void CDODGameRules::State_Enter_PREGAME( void )
  1988. {
  1989. m_flNextPeriodicThink = gpGlobals->curtime + 0.1;
  1990. Load_EntText();
  1991. }
  1992. void CDODGameRules::State_Think_PREGAME( void )
  1993. {
  1994. CheckLevelInitialized();
  1995. if( CountActivePlayers() > 0 )
  1996. State_Transition( STATE_STARTGAME );
  1997. }
  1998. // STARTGAME - wait a bit and then spawn everyone into the
  1999. // preround
  2000. void CDODGameRules::State_Enter_STARTGAME( void )
  2001. {
  2002. m_flStateTransitionTime = gpGlobals->curtime + 5 * dod_enableroundwaittime.GetFloat();
  2003. m_bInitialSpawn = true;
  2004. }
  2005. void CDODGameRules::State_Think_STARTGAME()
  2006. {
  2007. if( gpGlobals->curtime > m_flStateTransitionTime )
  2008. {
  2009. if( mp_warmup_time.GetFloat() > 0 )
  2010. {
  2011. // go into warmup, reset at end of it
  2012. SetInWarmup( true );
  2013. }
  2014. State_Transition( STATE_PREROUND );
  2015. }
  2016. }
  2017. void CDODGameRules::State_Enter_PREROUND( void )
  2018. {
  2019. // Longer wait time if its the first round, let people join
  2020. if ( m_bInitialSpawn )
  2021. {
  2022. m_flStateTransitionTime = gpGlobals->curtime + 10 * dod_enableroundwaittime.GetFloat();
  2023. m_bInitialSpawn = false;
  2024. }
  2025. else
  2026. {
  2027. m_flStateTransitionTime = gpGlobals->curtime + 5 * dod_enableroundwaittime.GetFloat();
  2028. }
  2029. //Game rules may change, if a new one becomes mastered at the end of the last round
  2030. DetectGameRules();
  2031. //reset everything in the level
  2032. RoundRespawn();
  2033. // reset this now! If its reset at round restart, we lose all the players that died
  2034. // during the preround
  2035. m_iAlliesRespawnHead = 0;
  2036. m_iAlliesRespawnTail = 0;
  2037. m_iAxisRespawnHead = 0;
  2038. m_iAxisRespawnTail = 0;
  2039. m_iNumAlliesRespawnWaves = 0;
  2040. m_iNumAxisRespawnWaves = 0;
  2041. m_iLastAlliesCapEvent = CAP_EVENT_NONE;
  2042. m_iLastAxisCapEvent = CAP_EVENT_NONE;
  2043. //find all the control points, init the timer
  2044. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" );
  2045. if( !pEnt )
  2046. {
  2047. Warning( "No dod_control_point_master found in level - control points will not work as expected.\n" );
  2048. }
  2049. bool bFoundTimer = false;
  2050. while( pEnt )
  2051. {
  2052. variant_t emptyVariant;
  2053. pEnt->AcceptInput( "RoundInit", NULL, NULL, emptyVariant, 0 );
  2054. CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( pEnt );
  2055. if ( pMaster && pMaster->IsActive() )
  2056. {
  2057. if ( pMaster->IsUsingRoundTimer() )
  2058. {
  2059. bFoundTimer = true;
  2060. m_bUsingTimer = true;
  2061. int iTimerSeconds;
  2062. pMaster->GetTimerData( iTimerSeconds, m_iTimerWinTeam );
  2063. if ( m_iTimerWinTeam != TEAM_ALLIES && m_iTimerWinTeam != TEAM_AXIS )
  2064. {
  2065. Assert( !"Round timer win team can only be allies or axis!\n" );
  2066. }
  2067. // Timer starts paused
  2068. if ( !m_pRoundTimer.Get() )
  2069. {
  2070. m_pRoundTimer = ( CDODRoundTimer *) CreateEntityByName( "dod_round_timer" );
  2071. }
  2072. Assert( m_pRoundTimer );
  2073. if ( m_pRoundTimer )
  2074. {
  2075. m_pRoundTimer->SetTimeRemaining( iTimerSeconds );
  2076. m_pRoundTimer->PauseTimer();
  2077. m_bPlayTimerWarning_1Minute = ( iTimerSeconds > 60 );
  2078. m_bPlayTimerWarning_2Minute = ( iTimerSeconds > 120 );
  2079. }
  2080. }
  2081. }
  2082. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" );
  2083. }
  2084. if ( bFoundTimer == false )
  2085. {
  2086. // No masters are active that require the round timer, destroy it
  2087. UTIL_Remove( m_pRoundTimer.Get() );
  2088. m_pRoundTimer = NULL;
  2089. }
  2090. //init the cap areas
  2091. pEnt = gEntList.FindEntityByClassname( NULL, "dod_capture_area" );
  2092. while( pEnt )
  2093. {
  2094. variant_t emptyVariant;
  2095. pEnt->AcceptInput( "RoundInit", NULL, NULL, emptyVariant, 0 );
  2096. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_capture_area" );
  2097. }
  2098. IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_start" );
  2099. if ( event )
  2100. gameeventmanager->FireEvent( event );
  2101. // figure out which teams are bombing
  2102. m_bAlliesAreBombing = false;
  2103. m_bAxisAreBombing = false;
  2104. pEnt = gEntList.FindEntityByClassname( NULL, "dod_bomb_target" );
  2105. while( pEnt )
  2106. {
  2107. CDODBombTarget *pTarget = dynamic_cast<CDODBombTarget *>( pEnt );
  2108. if ( pTarget && pTarget->State_Get() == BOMB_TARGET_ACTIVE )
  2109. {
  2110. switch( pTarget->GetBombingTeam() )
  2111. {
  2112. case TEAM_ALLIES:
  2113. m_bAlliesAreBombing = true;
  2114. break;
  2115. case TEAM_AXIS:
  2116. m_bAxisAreBombing = true;
  2117. break;
  2118. default:
  2119. break;
  2120. }
  2121. }
  2122. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" );
  2123. }
  2124. }
  2125. void CDODGameRules::State_Think_PREROUND( void )
  2126. {
  2127. if( gpGlobals->curtime > m_flStateTransitionTime )
  2128. State_Transition( STATE_RND_RUNNING );
  2129. CheckRespawnWaves();
  2130. }
  2131. void CDODGameRules::State_Enter_RND_RUNNING( void )
  2132. {
  2133. //find all the control points, init the timer
  2134. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" );
  2135. while( pEnt )
  2136. {
  2137. variant_t emptyVariant;
  2138. pEnt->AcceptInput( "RoundStart", NULL, NULL, emptyVariant, 0 );
  2139. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" );
  2140. }
  2141. IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_active" );
  2142. if ( event )
  2143. gameeventmanager->FireEvent( event );
  2144. if( !IsInWarmup() )
  2145. PlayStartRoundVoice();
  2146. if ( m_bUsingTimer && m_pRoundTimer.Get() != NULL )
  2147. {
  2148. m_pRoundTimer->ResumeTimer();
  2149. }
  2150. m_bChangeLevelOnRoundEnd = false;
  2151. }
  2152. void CDODGameRules::State_Think_RND_RUNNING( void )
  2153. {
  2154. //Where the magic happens
  2155. if ( m_bUsingTimer && m_pRoundTimer )
  2156. {
  2157. float flSecondsRemaining = m_pRoundTimer->GetTimeRemaining();
  2158. if ( flSecondsRemaining <= 0 )
  2159. {
  2160. // if there is a bomb still on a timer, and that bomb has
  2161. // the potential to add time, then we don't end the game
  2162. bool bBombBlocksWin = false;
  2163. //find all the control points, init the timer
  2164. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_bomb_target" );
  2165. while( pEnt )
  2166. {
  2167. CDODBombTarget *pBomb = dynamic_cast<CDODBombTarget *>( pEnt );
  2168. // Find active bombs that have the potential to add round time
  2169. if ( pBomb && pBomb->State_Get() == BOMB_TARGET_ARMED )
  2170. {
  2171. if ( pBomb->GetTimerAddSeconds() > 0 )
  2172. {
  2173. // don't end the round until this bomb goes off or is disarmed
  2174. bBombBlocksWin = true;
  2175. break;
  2176. }
  2177. CControlPoint *pPoint = pBomb->GetControlPoint();
  2178. int iBombingTeam = pBomb->GetBombingTeam();
  2179. if ( pPoint && pPoint->GetBombsRemaining() <= 1 )
  2180. {
  2181. // find active dod_control_point_masters, ask them if this flag capping
  2182. // would end the game
  2183. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" );
  2184. while( pEnt )
  2185. {
  2186. CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( pEnt );
  2187. if ( pMaster->IsActive() )
  2188. {
  2189. // Check TeamOwnsAllPoints, while overriding this particular flag's owner
  2190. if ( pMaster->WouldNewCPOwnerWinGame( pPoint, iBombingTeam ) )
  2191. {
  2192. // This bomb may win the game, don't end the round.
  2193. bBombBlocksWin = true;
  2194. break;
  2195. }
  2196. }
  2197. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" );
  2198. }
  2199. }
  2200. }
  2201. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" );
  2202. }
  2203. if ( bBombBlocksWin == false )
  2204. {
  2205. SetWinningTeam( m_iTimerWinTeam );
  2206. // tell the dod_control_point_master to fire its outputs for the winning team!
  2207. // minor hackage - dod_gamerules should be responsible for team win events, not dod_cpm
  2208. //find all the control points, init the timer
  2209. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" );
  2210. while( pEnt )
  2211. {
  2212. CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( pEnt );
  2213. if ( pMaster->IsActive() )
  2214. {
  2215. pMaster->FireTeamWinOutput( m_iTimerWinTeam );
  2216. }
  2217. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" );
  2218. }
  2219. }
  2220. }
  2221. else if ( flSecondsRemaining < 60.0 && m_bPlayTimerWarning_1Minute == true )
  2222. {
  2223. // play one minute warning
  2224. DevMsg( 1, "Timer Warning: 1 Minute Remaining\n" );
  2225. IGameEvent *event = gameeventmanager->CreateEvent( "dod_timer_flash" );
  2226. if ( event )
  2227. {
  2228. event->SetInt( "time_remaining", 60 );
  2229. gameeventmanager->FireEvent( event );
  2230. }
  2231. m_bPlayTimerWarning_1Minute = false;
  2232. }
  2233. else if ( flSecondsRemaining < 120.0 && m_bPlayTimerWarning_2Minute == true )
  2234. {
  2235. // play two minute warning
  2236. DevMsg( 1, "Timer Warning: 2 Minutes Remaining\n" );
  2237. IGameEvent *event = gameeventmanager->CreateEvent( "dod_timer_flash" );
  2238. if ( event )
  2239. {
  2240. event->SetInt( "time_remaining", 120 );
  2241. gameeventmanager->FireEvent( event );
  2242. }
  2243. m_bPlayTimerWarning_2Minute = false;
  2244. }
  2245. }
  2246. //if we don't find any active players, return to STATE_PREGAME
  2247. if( CountActivePlayers() <= 0 )
  2248. {
  2249. State_Transition( STATE_PREGAME );
  2250. return;
  2251. }
  2252. CheckRespawnWaves();
  2253. // check round restart
  2254. if( m_flRestartRoundTime > 0 && m_flRestartRoundTime < gpGlobals->curtime )
  2255. {
  2256. // time to restart!
  2257. State_Transition( STATE_RESTART );
  2258. m_flRestartRoundTime = -1;
  2259. }
  2260. // check ready restart
  2261. if( m_bAwaitingReadyRestart && m_bHeardAlliesReady && m_bHeardAxisReady )
  2262. {
  2263. //State_Transition( STATE_RESTART );
  2264. m_flRestartRoundTime = gpGlobals->curtime + 5;
  2265. m_bAwaitingReadyRestart = false;
  2266. }
  2267. }
  2268. void CDODGameRules::CheckRespawnWaves( void )
  2269. {
  2270. bool bDoFailSafeWaveCheck = false;
  2271. if ( m_flNextFailSafeWaveCheckTime < gpGlobals->curtime )
  2272. {
  2273. bDoFailSafeWaveCheck = true;
  2274. m_flNextFailSafeWaveCheckTime = gpGlobals->curtime + 3.0;
  2275. }
  2276. //Respawn Timers
  2277. if( m_iNumAlliesRespawnWaves > 0 )
  2278. {
  2279. if ( m_AlliesRespawnQueue[m_iAlliesRespawnHead] < gpGlobals->curtime )
  2280. {
  2281. DevMsg( "Wave: Respawning Allies\n" );
  2282. RespawnTeam( TEAM_ALLIES );
  2283. PopWaveTime( TEAM_ALLIES );
  2284. }
  2285. }
  2286. else if ( bDoFailSafeWaveCheck )
  2287. {
  2288. // if there are any allied people waiting to spawn, spawn them
  2289. FailSafeSpawnPlayersOnTeam( TEAM_ALLIES );
  2290. }
  2291. if( m_iNumAxisRespawnWaves > 0 )
  2292. {
  2293. if ( m_AxisRespawnQueue[m_iAxisRespawnHead] < gpGlobals->curtime )
  2294. {
  2295. DevMsg( "Wave: Respawning Axis\n" );
  2296. RespawnTeam( TEAM_AXIS );
  2297. PopWaveTime( TEAM_AXIS );
  2298. }
  2299. }
  2300. else if ( bDoFailSafeWaveCheck )
  2301. {
  2302. // if there are any axis people waiting to spawn, spawn them
  2303. FailSafeSpawnPlayersOnTeam( TEAM_AXIS );
  2304. }
  2305. }
  2306. void CDODGameRules::FailSafeSpawnPlayersOnTeam( int iTeam )
  2307. {
  2308. DODRoundState roundState = State_Get();
  2309. CDODTeam *pTeam = GetGlobalDODTeam( iTeam );
  2310. if ( pTeam )
  2311. {
  2312. int iNumPlayers = pTeam->GetNumPlayers();
  2313. for ( int i=0;i<iNumPlayers;i++ )
  2314. {
  2315. CDODPlayer *pPlayer = pTeam->GetDODPlayer(i);
  2316. if ( !pPlayer )
  2317. continue;
  2318. // if this player is waiting to spawn, spawn them
  2319. if ( pPlayer->IsAlive() )
  2320. continue;
  2321. if( pPlayer->m_Shared.DesiredPlayerClass() == PLAYERCLASS_UNDEFINED )
  2322. continue;
  2323. if ( gpGlobals->curtime < ( pPlayer->GetDeathTime() + DEATH_CAM_TIME ) )
  2324. continue;
  2325. if ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM )
  2326. continue;
  2327. if ( roundState != STATE_PREROUND && pPlayer->State_Get() == STATE_DEATH_ANIM )
  2328. continue;
  2329. // Respawn this player
  2330. pPlayer->DODRespawn();
  2331. Assert( !"This will happen, but see if we can figure out why we get here" );
  2332. }
  2333. }
  2334. }
  2335. //ALLIES WIN
  2336. void CDODGameRules::State_Enter_ALLIES_WIN( void )
  2337. {
  2338. float flTime = MAX( 5, dod_bonusroundtime.GetFloat() );
  2339. m_flStateTransitionTime = gpGlobals->curtime + flTime * dod_enableroundwaittime.GetFloat();
  2340. if ( m_bUsingTimer && m_pRoundTimer )
  2341. {
  2342. m_pRoundTimer->PauseTimer();
  2343. }
  2344. }
  2345. void CDODGameRules::State_Think_ALLIES_WIN( void )
  2346. {
  2347. if( gpGlobals->curtime > m_flStateTransitionTime )
  2348. {
  2349. State_Transition( STATE_PREROUND );
  2350. }
  2351. }
  2352. //AXIS WIN
  2353. void CDODGameRules::State_Enter_AXIS_WIN( void )
  2354. {
  2355. float flTime = MAX( 5, dod_bonusroundtime.GetFloat() );
  2356. m_flStateTransitionTime = gpGlobals->curtime + flTime * dod_enableroundwaittime.GetFloat();
  2357. if ( m_bUsingTimer && m_pRoundTimer )
  2358. {
  2359. m_pRoundTimer->PauseTimer();
  2360. }
  2361. }
  2362. void CDODGameRules::State_Think_AXIS_WIN( void )
  2363. {
  2364. if( gpGlobals->curtime > m_flStateTransitionTime )
  2365. {
  2366. State_Transition( STATE_PREROUND );
  2367. }
  2368. }
  2369. // manual restart
  2370. void CDODGameRules::State_Enter_RESTART( void )
  2371. {
  2372. // send scores
  2373. SendTeamScoresEvent();
  2374. // send restart event
  2375. IGameEvent *event = gameeventmanager->CreateEvent( "dod_restart_round" );
  2376. if ( event )
  2377. gameeventmanager->FireEvent( event );
  2378. SetInWarmup( false );
  2379. ResetScores();
  2380. // reset the round time
  2381. ResetMapTime();
  2382. State_Transition( STATE_PREROUND );
  2383. }
  2384. void CDODGameRules::SendTeamScoresEvent( void )
  2385. {
  2386. // send scores
  2387. IGameEvent *event = gameeventmanager->CreateEvent( "dod_team_scores" );
  2388. if ( event )
  2389. {
  2390. CDODTeam *pAllies = GetGlobalDODTeam( TEAM_ALLIES );
  2391. CDODTeam *pAxis = GetGlobalDODTeam( TEAM_AXIS );
  2392. Assert( pAllies && pAxis );
  2393. event->SetInt( "allies_caps", pAllies->GetRoundsWon() );
  2394. event->SetInt( "allies_tick", pAllies->GetScore() );
  2395. event->SetInt( "allies_players", pAllies->GetNumPlayers() );
  2396. event->SetInt( "axis_caps", pAxis->GetRoundsWon() );
  2397. event->SetInt( "axis_tick", pAxis->GetScore() );
  2398. event->SetInt( "axis_players", pAxis->GetNumPlayers() );
  2399. gameeventmanager->FireEvent( event );
  2400. }
  2401. }
  2402. void CDODGameRules::State_Think_RESTART( void )
  2403. {
  2404. Assert( 0 ); // should never get here, State_Enter_RESTART sets us into a different state
  2405. }
  2406. void CDODGameRules::ResetScores( void )
  2407. {
  2408. GetGlobalDODTeam( TEAM_ALLIES )->ResetScores();
  2409. GetGlobalDODTeam( TEAM_AXIS )->ResetScores();
  2410. CDODPlayer *pDODPlayer;
  2411. for( int i = 1; i <= gpGlobals->maxClients; i++ )
  2412. {
  2413. pDODPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  2414. if (pDODPlayer == NULL)
  2415. continue;
  2416. if (FNullEnt( pDODPlayer->edict() ))
  2417. continue;
  2418. pDODPlayer->ResetScores();
  2419. }
  2420. }
  2421. ConVar dod_showcleanedupents( "dod_showcleanedupents", "0", 0, "Show entities that are removed on round respawn" );
  2422. // Utility function
  2423. bool FindInList( const char **pStrings, const char *pToFind )
  2424. {
  2425. int i = 0;
  2426. while ( pStrings[i][0] != 0 )
  2427. {
  2428. if ( Q_stricmp( pStrings[i], pToFind ) == 0 )
  2429. return true;
  2430. i++;
  2431. }
  2432. return false;
  2433. }
  2434. void CDODGameRules::CleanUpMap()
  2435. {
  2436. // Recreate all the map entities from the map data (preserving their indices),
  2437. // then remove everything else except the players.
  2438. if( dod_showcleanedupents.GetBool() )
  2439. {
  2440. Msg( "CleanUpMap\n===============\n" );
  2441. }
  2442. // Get rid of all entities except players.
  2443. CBaseEntity *pCur = gEntList.FirstEnt();
  2444. while ( pCur )
  2445. {
  2446. if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) )
  2447. {
  2448. if( dod_showcleanedupents.GetBool() )
  2449. {
  2450. Msg( "Removed Entity: %s\n", pCur->GetClassname() );
  2451. }
  2452. UTIL_Remove( pCur );
  2453. }
  2454. pCur = gEntList.NextEnt( pCur );
  2455. }
  2456. // Really remove the entities so we can have access to their slots below.
  2457. gEntList.CleanupDeleteList();
  2458. // Now reload the map entities.
  2459. class CDODMapEntityFilter : public IMapEntityFilter
  2460. {
  2461. public:
  2462. virtual bool ShouldCreateEntity( const char *pClassname )
  2463. {
  2464. // Don't recreate the preserved entities.
  2465. if ( !FindInList( s_PreserveEnts, pClassname ) )
  2466. {
  2467. return true;
  2468. }
  2469. else
  2470. {
  2471. // Increment our iterator since it's not going to call CreateNextEntity for this ent.
  2472. if ( m_iIterator != g_MapEntityRefs.InvalidIndex() )
  2473. m_iIterator = g_MapEntityRefs.Next( m_iIterator );
  2474. return false;
  2475. }
  2476. }
  2477. virtual CBaseEntity* CreateNextEntity( const char *pClassname )
  2478. {
  2479. if ( m_iIterator == g_MapEntityRefs.InvalidIndex() )
  2480. {
  2481. // This shouldn't be possible. When we loaded the map, it should have used
  2482. // CDODMapLoadEntityFilter, which should have built the g_MapEntityRefs list
  2483. // with the same list of entities we're referring to here.
  2484. Assert( false );
  2485. return NULL;
  2486. }
  2487. else
  2488. {
  2489. CMapEntityRef &ref = g_MapEntityRefs[m_iIterator];
  2490. m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity.
  2491. if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) )
  2492. {
  2493. // Doh! The entity was delete and its slot was reused.
  2494. // Just use any old edict slot. This case sucks because we lose the baseline.
  2495. return CreateEntityByName( pClassname );
  2496. }
  2497. else
  2498. {
  2499. // Cool, the slot where this entity was is free again (most likely, the entity was
  2500. // freed above). Now create an entity with this specific index.
  2501. return CreateEntityByName( pClassname, ref.m_iEdict );
  2502. }
  2503. }
  2504. }
  2505. public:
  2506. int m_iIterator; // Iterator into g_MapEntityRefs.
  2507. };
  2508. CDODMapEntityFilter filter;
  2509. filter.m_iIterator = g_MapEntityRefs.Head();
  2510. // DO NOT CALL SPAWN ON info_node ENTITIES!
  2511. MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true );
  2512. }
  2513. int CDODGameRules::CountActivePlayers( void )
  2514. {
  2515. int i;
  2516. int count = 0;
  2517. CDODPlayer *pDODPlayer;
  2518. for (i = 1; i <= gpGlobals->maxClients; i++ )
  2519. {
  2520. pDODPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  2521. if( pDODPlayer )
  2522. {
  2523. if( pDODPlayer->IsReadyToPlay() )
  2524. {
  2525. count++;
  2526. }
  2527. }
  2528. }
  2529. return count;
  2530. }
  2531. void CDODGameRules::RoundRespawn( void )
  2532. {
  2533. CleanUpMap();
  2534. RespawnAllPlayers();
  2535. // reset per-round scores for each player
  2536. for ( int i=0;i<MAX_PLAYERS;i++ )
  2537. {
  2538. CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  2539. if ( pPlayer )
  2540. {
  2541. pPlayer->ResetPerRoundStats();
  2542. }
  2543. }
  2544. }
  2545. typedef struct {
  2546. int iPlayerIndex;
  2547. int iScore;
  2548. } playerscore_t;
  2549. int PlayerScoreInfoSort( const playerscore_t *p1, const playerscore_t *p2 )
  2550. {
  2551. // check frags
  2552. if ( p1->iScore > p2->iScore )
  2553. return -1;
  2554. if ( p2->iScore > p1->iScore )
  2555. return 1;
  2556. // check index
  2557. if ( p1->iPlayerIndex < p2->iPlayerIndex )
  2558. return -1;
  2559. return 1;
  2560. }
  2561. // Store which event happened most recently, flag cap or bomb explode
  2562. void CDODGameRules::CapEvent( int event, int team )
  2563. {
  2564. switch( team )
  2565. {
  2566. case TEAM_ALLIES:
  2567. m_iLastAlliesCapEvent = event;
  2568. break;
  2569. case TEAM_AXIS:
  2570. m_iLastAxisCapEvent = event;
  2571. break;
  2572. }
  2573. }
  2574. void FillEventCategory( IGameEvent *event, int side, int category, CUtlVector<playerscore_t> &pList )
  2575. {
  2576. switch ( side )
  2577. {
  2578. case 0:
  2579. event->SetInt( "category_left", category );
  2580. break;
  2581. case 1:
  2582. event->SetInt( "category_right", category );
  2583. break;
  2584. }
  2585. static const char *pCategoryNames[2][6] =
  2586. {
  2587. {
  2588. "left_1",
  2589. "left_score_1",
  2590. "left_2",
  2591. "left_score_2",
  2592. "left_3",
  2593. "left_score_3"
  2594. },
  2595. {
  2596. "right_1",
  2597. "right_score_1",
  2598. "right_2",
  2599. "right_score_2",
  2600. "right_3",
  2601. "right_score_3"
  2602. }
  2603. };
  2604. int iNumInList = pList.Count();
  2605. if ( iNumInList > 0 )
  2606. {
  2607. event->SetInt( pCategoryNames[side][0], pList[0].iPlayerIndex );
  2608. event->SetInt( pCategoryNames[side][1], pList[0].iScore );
  2609. }
  2610. else
  2611. event->SetInt( pCategoryNames[side][0], 0 );
  2612. if ( iNumInList > 1 )
  2613. {
  2614. event->SetInt( pCategoryNames[side][2], pList[1].iPlayerIndex );
  2615. event->SetInt( pCategoryNames[side][3], pList[1].iScore );
  2616. }
  2617. else
  2618. event->SetInt( pCategoryNames[side][2], 0 );
  2619. if ( iNumInList > 2 )
  2620. {
  2621. event->SetInt( pCategoryNames[side][4], pList[2].iPlayerIndex );
  2622. event->SetInt( pCategoryNames[side][5], pList[2].iScore );
  2623. }
  2624. else
  2625. event->SetInt( pCategoryNames[side][4], 0 );
  2626. }
  2627. //Input for other entities to declare a round winner.
  2628. //Most often a dod_control_point_master saying that the
  2629. //round timer expired or that someone capped all the flags
  2630. void CDODGameRules::SetWinningTeam( int team )
  2631. {
  2632. if ( team != TEAM_ALLIES && team != TEAM_AXIS )
  2633. {
  2634. Assert( !"bad winning team set" );
  2635. return;
  2636. }
  2637. PlayWinSong(team);
  2638. GetGlobalDODTeam( team )->IncrementRoundsWon();
  2639. switch(team)
  2640. {
  2641. case TEAM_ALLIES:
  2642. {
  2643. State_Transition( STATE_ALLIES_WIN );
  2644. }
  2645. break;
  2646. case TEAM_AXIS:
  2647. {
  2648. State_Transition( STATE_AXIS_WIN );
  2649. }
  2650. break;
  2651. default:
  2652. break;
  2653. }
  2654. IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_win" );
  2655. if ( event )
  2656. {
  2657. event->SetInt( "team", team );
  2658. gameeventmanager->FireEvent( event );
  2659. }
  2660. // if this was in colmar, and the losing team did not cap any points,
  2661. // the winners may have gotten an achievement
  2662. if ( FStrEq( STRING(gpGlobals->mapname), "dod_colmar" ) )
  2663. {
  2664. CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ) );
  2665. if ( pMaster )
  2666. {
  2667. // 1. losing team must not own any control points
  2668. // 2. for each point that the winning team owns, that takes bombs, it must still have 2 bombs required
  2669. bool bFlawlessVictory = true;
  2670. int iNumCP = pMaster->GetNumPoints();
  2671. for ( int i=0;i<iNumCP;i++ )
  2672. {
  2673. CControlPoint *pPoint = pMaster->GetCPByIndex(i);
  2674. if ( !pPoint || !pPoint->PointIsVisible() )
  2675. continue;
  2676. // if the enemy owns any visible points, not a flawless victory
  2677. if ( pPoint->GetOwner() != team )
  2678. {
  2679. bFlawlessVictory = false;
  2680. }
  2681. // 0 bombs remaining means we blew it up and now own it.
  2682. // 1 bomb remaining means we own it, but the other team blew it up a bit.
  2683. else if ( pPoint->GetBombsRequired() > 0 && pPoint->GetBombsRemaining() == 1 )
  2684. {
  2685. bFlawlessVictory = false;
  2686. }
  2687. }
  2688. if ( bFlawlessVictory )
  2689. {
  2690. GetGlobalDODTeam( team )->AwardAchievement( ACHIEVEMENT_DOD_COLMAR_DEFENSE );
  2691. }
  2692. }
  2693. }
  2694. // send team scores
  2695. SendTeamScoresEvent();
  2696. IGameEvent *winEvent = gameeventmanager->CreateEvent( "dod_win_panel" );
  2697. if ( winEvent )
  2698. {
  2699. // determine what categories to send
  2700. if ( m_bUsingTimer )
  2701. {
  2702. if ( team == m_iTimerWinTeam )
  2703. {
  2704. // timer expired, defenders win
  2705. // show total time that was defended
  2706. winEvent->SetBool( "show_timer_defend", true );
  2707. winEvent->SetInt( "timer_time", m_pRoundTimer->GetTimerMaxLength() );
  2708. }
  2709. else
  2710. {
  2711. // attackers win
  2712. // show time it took for them to win
  2713. winEvent->SetBool( "show_timer_attack", true );
  2714. int iTimeElapsed = m_pRoundTimer->GetTimerMaxLength() - (int)m_pRoundTimer->GetTimeRemaining();
  2715. winEvent->SetInt( "timer_time", iTimeElapsed );
  2716. }
  2717. }
  2718. else
  2719. {
  2720. winEvent->SetBool( "show_timer_attack", false );
  2721. winEvent->SetBool( "show_timer_defend", false );
  2722. }
  2723. int iLastEvent = ( team == TEAM_ALLIES ) ? m_iLastAlliesCapEvent : m_iLastAxisCapEvent;
  2724. winEvent->SetInt( "final_event", iLastEvent );
  2725. int i;
  2726. int index;
  2727. CUtlVector<playerscore_t> m_TopCappers;
  2728. CUtlVector<playerscore_t> m_TopDefenders;
  2729. CUtlVector<playerscore_t> m_TopBombers;
  2730. CUtlVector<playerscore_t> m_TopKills;
  2731. CDODTeam *pWinningTeam = GetGlobalDODTeam( team );
  2732. int iNumPlayers = pWinningTeam->GetNumPlayers();
  2733. for ( i=0;i<iNumPlayers;i++ )
  2734. {
  2735. CDODPlayer *pPlayer = dynamic_cast<CDODPlayer *>( pWinningTeam->GetPlayer(i) );
  2736. if ( pPlayer )
  2737. {
  2738. int iCaps = pPlayer->GetPerRoundCaps();
  2739. if ( iCaps )
  2740. {
  2741. index = m_TopCappers.AddToTail();
  2742. m_TopCappers[index].iPlayerIndex = pPlayer->entindex();
  2743. m_TopCappers[index].iScore = iCaps;
  2744. }
  2745. int iDefenses = pPlayer->GetPerRoundDefenses();
  2746. if ( iDefenses )
  2747. {
  2748. index = m_TopDefenders.AddToTail();
  2749. m_TopDefenders[index].iPlayerIndex = pPlayer->entindex();
  2750. m_TopDefenders[index].iScore = iDefenses;
  2751. }
  2752. // bombs
  2753. int iBombsDetonated = pPlayer->GetPerRoundBombsDetonated();
  2754. if ( iBombsDetonated )
  2755. {
  2756. index = m_TopBombers.AddToTail();
  2757. m_TopBombers[index].iPlayerIndex = pPlayer->entindex();
  2758. m_TopBombers[index].iScore = iBombsDetonated;
  2759. }
  2760. // kills
  2761. int iKills = pPlayer->GetPerRoundKills();
  2762. if ( iKills )
  2763. {
  2764. index = m_TopKills.AddToTail();
  2765. m_TopKills[index].iPlayerIndex = pPlayer->entindex();
  2766. m_TopKills[index].iScore = iKills;
  2767. }
  2768. pPlayer->StatEvent_RoundWin();
  2769. }
  2770. }
  2771. CDODTeam *pLosingTeam = GetGlobalDODTeam( ( team == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES );
  2772. iNumPlayers = pLosingTeam->GetNumPlayers();
  2773. for ( i=0;i<iNumPlayers;i++ )
  2774. {
  2775. CDODPlayer *pPlayer = dynamic_cast<CDODPlayer *>( pLosingTeam->GetPlayer(i) );
  2776. if ( pPlayer )
  2777. {
  2778. pPlayer->StatEvent_RoundLoss();
  2779. }
  2780. }
  2781. m_TopCappers.Sort( PlayerScoreInfoSort );
  2782. m_TopDefenders.Sort( PlayerScoreInfoSort );
  2783. m_TopBombers.Sort( PlayerScoreInfoSort );
  2784. m_TopKills.Sort( PlayerScoreInfoSort );
  2785. // Decide what two categories to show in the winpanel
  2786. // based on the gametype and which event have good information
  2787. // to show
  2788. int iCategoryPriority[8];
  2789. int pos = 0;
  2790. // Default is to show two blank sides
  2791. iCategoryPriority[pos++] = WINPANEL_TOP3_NONE;
  2792. iCategoryPriority[pos++] = WINPANEL_TOP3_NONE;
  2793. // Only show a category if it has information in it
  2794. if ( m_TopKills.Count() > 0 )
  2795. {
  2796. iCategoryPriority[pos++] = WINPANEL_TOP3_KILLERS;
  2797. }
  2798. if ( m_TopDefenders.Count() > 0 )
  2799. {
  2800. iCategoryPriority[pos++] = WINPANEL_TOP3_DEFENDERS;
  2801. }
  2802. if ( m_TopBombers.Count() > 0 )
  2803. {
  2804. iCategoryPriority[pos++] = WINPANEL_TOP3_BOMBERS;
  2805. }
  2806. else if ( m_TopCappers.Count() > 0 )
  2807. {
  2808. iCategoryPriority[pos++] = WINPANEL_TOP3_CAPPERS;
  2809. }
  2810. // Get the two most interesting
  2811. int iLeftCategory = iCategoryPriority[pos-1];
  2812. int iRightCategory = iCategoryPriority[pos-2];
  2813. switch( iLeftCategory )
  2814. {
  2815. case WINPANEL_TOP3_BOMBERS:
  2816. FillEventCategory( winEvent, 0, iLeftCategory, m_TopBombers );
  2817. break;
  2818. case WINPANEL_TOP3_CAPPERS:
  2819. FillEventCategory( winEvent, 0, iLeftCategory, m_TopCappers );
  2820. break;
  2821. case WINPANEL_TOP3_DEFENDERS:
  2822. FillEventCategory( winEvent, 0, iLeftCategory, m_TopDefenders );
  2823. break;
  2824. case WINPANEL_TOP3_KILLERS:
  2825. FillEventCategory( winEvent, 0, iLeftCategory, m_TopKills );
  2826. break;
  2827. case WINPANEL_TOP3_NONE:
  2828. default:
  2829. break;
  2830. }
  2831. switch( iRightCategory )
  2832. {
  2833. case WINPANEL_TOP3_BOMBERS:
  2834. FillEventCategory( winEvent, 1, iRightCategory, m_TopBombers );
  2835. break;
  2836. case WINPANEL_TOP3_CAPPERS:
  2837. FillEventCategory( winEvent, 1, iRightCategory, m_TopCappers );
  2838. break;
  2839. case WINPANEL_TOP3_DEFENDERS:
  2840. FillEventCategory( winEvent, 1, iRightCategory, m_TopDefenders );
  2841. break;
  2842. case WINPANEL_TOP3_KILLERS:
  2843. FillEventCategory( winEvent, 1, iRightCategory, m_TopKills );
  2844. break;
  2845. case WINPANEL_TOP3_NONE:
  2846. default:
  2847. break;
  2848. }
  2849. gameeventmanager->FireEvent( winEvent );
  2850. }
  2851. }
  2852. void TestWinpanel( void )
  2853. {
  2854. IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_win" );
  2855. event->SetInt( "team", TEAM_ALLIES );
  2856. if ( event )
  2857. {
  2858. gameeventmanager->FireEvent( event );
  2859. }
  2860. IGameEvent *event2 = gameeventmanager->CreateEvent( "dod_point_captured" );
  2861. if ( event2 )
  2862. {
  2863. char cappers[9]; // pCappingPlayers is max length 8
  2864. int i;
  2865. for( i=0;i<1;i++ )
  2866. {
  2867. cappers[i] = (char)1;
  2868. }
  2869. cappers[i] = '\0';
  2870. // pCappingPlayers is a null terminated list of player indeces
  2871. event2->SetString( "cappers", cappers );
  2872. event2->SetBool( "bomb", true );
  2873. gameeventmanager->FireEvent( event2 );
  2874. }
  2875. IGameEvent *winEvent = gameeventmanager->CreateEvent( "dod_win_panel" );
  2876. if ( winEvent )
  2877. {
  2878. if ( 1 )
  2879. {
  2880. if ( 0 /*team == m_iTimerWinTeam */)
  2881. {
  2882. // timer expired, defenders win
  2883. // show total time that was defended
  2884. winEvent->SetBool( "show_timer_defend", true );
  2885. winEvent->SetInt( "timer_time", 0 /*m_pRoundTimer->GetTimerMaxLength() */);
  2886. }
  2887. else
  2888. {
  2889. // attackers win
  2890. // show time it took for them to win
  2891. winEvent->SetBool( "show_timer_attack", true );
  2892. int iTimeElapsed = 90; //m_pRoundTimer->GetTimerMaxLength() - (int)m_pRoundTimer->GetTimeRemaining();
  2893. winEvent->SetInt( "timer_time", iTimeElapsed );
  2894. }
  2895. }
  2896. else
  2897. {
  2898. winEvent->SetBool( "show_timer_attack", false );
  2899. winEvent->SetBool( "show_timer_defend", false );
  2900. }
  2901. int iLastEvent = CAP_EVENT_FLAG;
  2902. winEvent->SetInt( "final_event", iLastEvent );
  2903. CUtlVector<playerscore_t> m_TopKillers;
  2904. CUtlVector<playerscore_t> m_TopDefenders;
  2905. CUtlVector<playerscore_t> m_TopCappers;
  2906. CUtlVector<playerscore_t> m_TopBombers;
  2907. CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex(1) );
  2908. if ( !pPlayer )
  2909. return;
  2910. int i = 0;
  2911. int index;
  2912. for ( i=0;i<3;i++ )
  2913. {
  2914. index = m_TopCappers.AddToTail();
  2915. m_TopCappers[index].iPlayerIndex = pPlayer->entindex();
  2916. m_TopCappers[index].iScore = pPlayer->GetPerRoundCaps() + 1;
  2917. index = m_TopDefenders.AddToTail();
  2918. m_TopDefenders[index].iPlayerIndex = pPlayer->entindex();
  2919. m_TopDefenders[index].iScore = pPlayer->GetPerRoundDefenses() + 1;
  2920. index = m_TopBombers.AddToTail();
  2921. m_TopBombers[index].iPlayerIndex = pPlayer->entindex();
  2922. m_TopBombers[index].iScore = pPlayer->GetPerRoundBombsDetonated() + 1;
  2923. index = m_TopKillers.AddToTail();
  2924. m_TopKillers[index].iPlayerIndex = pPlayer->entindex();
  2925. m_TopKillers[index].iScore = pPlayer->GetPerRoundKills() + 1;
  2926. }
  2927. m_TopCappers.Sort( PlayerScoreInfoSort );
  2928. m_TopDefenders.Sort( PlayerScoreInfoSort );
  2929. //FillEventCategory( winEvent, 0, WINPANEL_TOP3_KILLERS, m_TopKillers );
  2930. //FillEventCategory( winEvent, 1, WINPANEL_TOP3_DEFENDERS, m_TopDefenders );
  2931. FillEventCategory( winEvent, 0, WINPANEL_TOP3_BOMBERS, m_TopBombers );
  2932. FillEventCategory( winEvent, 1, WINPANEL_TOP3_CAPPERS, m_TopCappers );
  2933. gameeventmanager->FireEvent( winEvent );
  2934. }
  2935. }
  2936. ConCommand dod_test_winpanel( "dod_test_winpanel", TestWinpanel, "", FCVAR_CHEAT );
  2937. // bForceRespawn - respawn player even if dead or dying
  2938. // bTeam - if true, only respawn the passed team
  2939. // iTeam - team to respawn
  2940. void CDODGameRules::RespawnPlayers( bool bForceRespawn, bool bTeam /* = false */, int iTeam/* = TEAM_UNASSIGNED */ )
  2941. {
  2942. if ( bTeam )
  2943. {
  2944. if ( iTeam == TEAM_ALLIES )
  2945. DevMsg( 2, "Respawning Allies\n" );
  2946. else if ( iTeam == TEAM_AXIS )
  2947. DevMsg( 2, "Respawning Axis\n" );
  2948. else
  2949. Assert(!"Trying to respawn a strange team");
  2950. }
  2951. CDODPlayer *pPlayer;
  2952. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  2953. {
  2954. pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  2955. if ( !pPlayer )
  2956. continue;
  2957. // Check for team specific spawn
  2958. if ( bTeam && pPlayer->GetTeamNumber() != iTeam )
  2959. continue;
  2960. // players that haven't chosen a class can never spawn
  2961. if( pPlayer->m_Shared.DesiredPlayerClass() == PLAYERCLASS_UNDEFINED )
  2962. {
  2963. ClientPrint(pPlayer, HUD_PRINTTALK, "#game_will_spawn");
  2964. continue;
  2965. }
  2966. // If we aren't force respawning, don't respawn players that:
  2967. // - are alive
  2968. // - are still in the death anim stage of dying
  2969. if ( !bForceRespawn )
  2970. {
  2971. if ( pPlayer->IsAlive() )
  2972. continue;
  2973. if ( gpGlobals->curtime < ( pPlayer->GetDeathTime() + DEATH_CAM_TIME ) )
  2974. continue;
  2975. if ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM )
  2976. continue;
  2977. if ( State_Get() != STATE_PREROUND && pPlayer->State_Get() == STATE_DEATH_ANIM )
  2978. continue;
  2979. }
  2980. // Respawn this player
  2981. pPlayer->DODRespawn();
  2982. }
  2983. }
  2984. bool CDODGameRules::IsPlayerClassOnTeam( int cls, int team )
  2985. {
  2986. if( cls == PLAYERCLASS_RANDOM )
  2987. return true;
  2988. CDODTeam *pTeam = GetGlobalDODTeam( team );
  2989. return ( cls >= 0 && cls < pTeam->GetNumPlayerClasses() );
  2990. }
  2991. bool CDODGameRules::CanPlayerJoinClass( CDODPlayer *pPlayer, int cls )
  2992. {
  2993. if( cls == PLAYERCLASS_RANDOM )
  2994. {
  2995. return mp_allowrandomclass.GetBool();
  2996. }
  2997. if( ReachedClassLimit( pPlayer->GetTeamNumber(), cls ) )
  2998. return false;
  2999. return true;
  3000. }
  3001. bool CDODGameRules::ReachedClassLimit( int team, int cls )
  3002. {
  3003. Assert( cls != PLAYERCLASS_UNDEFINED );
  3004. Assert( cls != PLAYERCLASS_RANDOM );
  3005. // get the cvar
  3006. int iClassLimit = GetClassLimit( team, cls );
  3007. // count how many are active
  3008. int iClassExisting = CountPlayerClass( team, cls );
  3009. CDODTeam *pTeam = GetGlobalDODTeam( team );
  3010. const CDODPlayerClassInfo &pThisClassInfo = pTeam->GetPlayerClassInfo( cls );
  3011. if( mp_combinemglimits.GetBool() && pThisClassInfo.m_bClassLimitMGMerge )
  3012. {
  3013. // find the other classes that have "mergemgclasses"
  3014. for( int i=0; i<pTeam->GetNumPlayerClasses();i++ )
  3015. {
  3016. if( i != cls )
  3017. {
  3018. const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( i );
  3019. if( pClassInfo.m_bClassLimitMGMerge )
  3020. {
  3021. // add that class' limits and counts
  3022. iClassLimit += GetClassLimit( team, i );
  3023. iClassExisting += CountPlayerClass( team, i );
  3024. }
  3025. }
  3026. }
  3027. }
  3028. if( iClassLimit > -1 && iClassExisting >= iClassLimit )
  3029. {
  3030. return true;
  3031. }
  3032. return false;
  3033. }
  3034. int CDODGameRules::CountPlayerClass( int team, int cls )
  3035. {
  3036. int num = 0;
  3037. CDODPlayer *pDODPlayer;
  3038. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  3039. {
  3040. pDODPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  3041. if (pDODPlayer == NULL)
  3042. continue;
  3043. if (FNullEnt( pDODPlayer->edict() ))
  3044. continue;
  3045. if( pDODPlayer->GetTeamNumber() != team )
  3046. continue;
  3047. if( pDODPlayer->m_Shared.DesiredPlayerClass() == cls )
  3048. num++;
  3049. }
  3050. return num;
  3051. }
  3052. int CDODGameRules::GetClassLimit( int team, int cls )
  3053. {
  3054. CDODTeam *pTeam = GetGlobalDODTeam( team );
  3055. Assert( pTeam );
  3056. const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( cls );
  3057. int iClassLimit;
  3058. ConVar *pLimitCvar = ( ConVar * )cvar->FindVar( pClassInfo.m_szLimitCvar );
  3059. Assert( pLimitCvar );
  3060. if( pLimitCvar )
  3061. iClassLimit = pLimitCvar->GetInt();
  3062. else
  3063. iClassLimit = -1;
  3064. return iClassLimit;
  3065. }
  3066. void CDODGameRules::CheckLevelInitialized()
  3067. {
  3068. if ( !m_bLevelInitialized )
  3069. {
  3070. // Count the number of spawn points for each team
  3071. // This determines the maximum number of players allowed on each
  3072. CBaseEntity* ent = NULL;
  3073. m_iSpawnPointCount_Allies = 0;
  3074. m_iSpawnPointCount_Axis = 0;
  3075. while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_allies" ) ) != NULL )
  3076. {
  3077. if ( IsSpawnPointValid( ent, NULL ) )
  3078. {
  3079. m_iSpawnPointCount_Allies++;
  3080. // store in a list
  3081. m_AlliesSpawnPoints.AddToTail( ent );
  3082. }
  3083. else
  3084. {
  3085. Warning("Invalid allies spawnpoint at (%.1f,%.1f,%.1f)\n",
  3086. ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] );
  3087. }
  3088. }
  3089. while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_axis" ) ) != NULL )
  3090. {
  3091. if ( IsSpawnPointValid( ent, NULL ) )
  3092. {
  3093. m_iSpawnPointCount_Axis++;
  3094. // store in a list
  3095. m_AxisSpawnPoints.AddToTail( ent );
  3096. }
  3097. else
  3098. {
  3099. Warning("Invalid axis spawnpoint at (%.1f,%.1f,%.1f)\n",
  3100. ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] );
  3101. }
  3102. }
  3103. m_bLevelInitialized = true;
  3104. }
  3105. }
  3106. CUtlVector<EHANDLE> *CDODGameRules::GetSpawnPointListForTeam( int iTeam )
  3107. {
  3108. switch ( iTeam )
  3109. {
  3110. case TEAM_ALLIES:
  3111. return &m_AlliesSpawnPoints;
  3112. case TEAM_AXIS:
  3113. return &m_AxisSpawnPoints;
  3114. default:
  3115. break;
  3116. }
  3117. return NULL;
  3118. }
  3119. /* create some proxy entities that we use for transmitting data */
  3120. void CDODGameRules::CreateStandardEntities()
  3121. {
  3122. // Create the player resource
  3123. g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "dod_player_manager", vec3_origin, vec3_angle );
  3124. // Create the objective resource
  3125. g_pObjectiveResource = (CDODObjectiveResource *)CBaseEntity::Create( "dod_objective_resource", vec3_origin, vec3_angle );
  3126. Assert( g_pObjectiveResource );
  3127. // Create the entity that will send our data to the client.
  3128. #ifdef DBGFLAG_ASSERT
  3129. CBaseEntity *pEnt =
  3130. #endif
  3131. CBaseEntity::Create( "dod_gamerules", vec3_origin, vec3_angle );
  3132. Assert( pEnt );
  3133. }
  3134. ConVar dod_waverespawnfactor( "dod_waverespawnfactor", "1.0", FCVAR_REPLICATED | FCVAR_CHEAT, "Factor for respawn wave timers" );
  3135. float CDODGameRules::GetWaveTime( int iTeam )
  3136. {
  3137. float flRespawnTime = 0.0f;
  3138. switch( iTeam )
  3139. {
  3140. case TEAM_ALLIES:
  3141. flRespawnTime = ( m_iNumAlliesRespawnWaves > 0 ) ? m_AlliesRespawnQueue[m_iAlliesRespawnHead] : -1;
  3142. break;
  3143. case TEAM_AXIS:
  3144. flRespawnTime = ( m_iNumAxisRespawnWaves > 0 ) ? m_AxisRespawnQueue[m_iAxisRespawnHead] : -1;
  3145. break;
  3146. default:
  3147. Assert( !"Why are you trying to get the wave time for a non-team?" );
  3148. break;
  3149. }
  3150. return flRespawnTime;
  3151. }
  3152. float CDODGameRules::GetMaxWaveTime( int nTeam )
  3153. {
  3154. float fTime = 0;
  3155. // Quick waves to get everyone in if we are in PREROUND
  3156. if ( State_Get() == STATE_PREROUND )
  3157. {
  3158. return 1.0;
  3159. }
  3160. int nNumPlayers = GetGlobalDODTeam( nTeam )->GetNumPlayers();
  3161. if( nNumPlayers < 3 )
  3162. fTime = 6.f;
  3163. else if( nNumPlayers < 6 )
  3164. fTime = 8.f;
  3165. else if( nNumPlayers < 8 )
  3166. fTime = 10.f;
  3167. else if( nNumPlayers < 10 )
  3168. fTime = 11.f;
  3169. else if( nNumPlayers < 12 )
  3170. fTime = 12.f;
  3171. else if( nNumPlayers < 14 )
  3172. fTime = 13.f;
  3173. else
  3174. fTime = 14.f;
  3175. //adjust wave time based on mapper settings
  3176. //they can adjust the factor ( default 1.0 )
  3177. // to give longer or shorter wait times for
  3178. // either team
  3179. if( nTeam == TEAM_ALLIES )
  3180. fTime *= m_GamePlayRules.m_fAlliesRespawnFactor;
  3181. else if( nTeam == TEAM_AXIS )
  3182. fTime *= m_GamePlayRules.m_fAxisRespawnFactor;
  3183. // Finally, adjust the respawn time based on how well the team is doing
  3184. // a team with more flags should respawn faster.
  3185. // Give a bonus to respawn time for each flag that we own that we
  3186. // don't own by default.
  3187. CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster*>( gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ) );
  3188. if( pMaster )
  3189. {
  3190. int advantageFlags = pMaster->CountAdvantageFlags( nTeam );
  3191. // this can be negative if we are losing, this will add time!
  3192. fTime -= (float)(advantageFlags) * dod_flagrespawnbonus.GetFloat();
  3193. }
  3194. fTime *= dod_waverespawnfactor.GetFloat();
  3195. // Minimum 5 seconds
  3196. if (fTime <= DEATH_CAM_TIME)
  3197. fTime = DEATH_CAM_TIME;
  3198. // Maximum 20 seconds
  3199. if ( fTime > MAX_WAVE_RESPAWN_TIME )
  3200. {
  3201. fTime = MAX_WAVE_RESPAWN_TIME;
  3202. }
  3203. return fTime;
  3204. }
  3205. void CDODGameRules::CreateOrJoinRespawnWave( CDODPlayer *pPlayer )
  3206. {
  3207. int team = pPlayer->GetTeamNumber();
  3208. float flWaveTime = GetWaveTime( team ) - gpGlobals->curtime;
  3209. if( flWaveTime <= 0 )
  3210. {
  3211. // start a new wave
  3212. DevMsg( "Wave: Starting a new wave for team %d, time %.1f\n", team, GetMaxWaveTime(team) );
  3213. //start a new wave with this player
  3214. AddWaveTime( team, GetMaxWaveTime(team) );
  3215. }
  3216. else
  3217. {
  3218. // see if this player needs to start a new wave
  3219. int team = pPlayer->GetTeamNumber();
  3220. float flSpawnEligibleTime = gpGlobals->curtime + DEATH_CAM_TIME;
  3221. if ( team == TEAM_ALLIES )
  3222. {
  3223. bool bFoundWave = false;
  3224. int i = m_iAlliesRespawnHead;
  3225. while( i != m_iAlliesRespawnTail )
  3226. {
  3227. // if the player can fit in this wave, set bFound = true
  3228. if ( flSpawnEligibleTime < m_AlliesRespawnQueue[i] )
  3229. {
  3230. bFoundWave = true;
  3231. break;
  3232. }
  3233. i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE;
  3234. }
  3235. if ( !bFoundWave )
  3236. {
  3237. // add a new wave to the end
  3238. AddWaveTime( TEAM_ALLIES, GetMaxWaveTime(TEAM_ALLIES) );
  3239. }
  3240. }
  3241. else if ( team == TEAM_AXIS )
  3242. {
  3243. bool bFoundWave = false;
  3244. int i = m_iAxisRespawnHead;
  3245. while( i != m_iAxisRespawnTail )
  3246. {
  3247. // if the player can fit in this wave, set bFound = true
  3248. if ( flSpawnEligibleTime < m_AxisRespawnQueue[i] )
  3249. {
  3250. bFoundWave = true;
  3251. break;
  3252. }
  3253. i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE;
  3254. }
  3255. if ( !bFoundWave )
  3256. {
  3257. // add a new wave to the end
  3258. AddWaveTime( TEAM_AXIS, GetMaxWaveTime(TEAM_AXIS) );
  3259. }
  3260. }
  3261. else
  3262. Assert( 0 );
  3263. }
  3264. }
  3265. bool CDODGameRules::InRoundRestart( void )
  3266. {
  3267. if ( State_Get() == STATE_PREROUND )
  3268. return true;
  3269. return false;
  3270. }
  3271. void CDODGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info )
  3272. {
  3273. CDODPlayer *pDODVictim = ToDODPlayer( pVictim );
  3274. // if you're still playing dod, you know how this works, let's not
  3275. // interfere with the freezecam panel
  3276. //bool bPlayed = pDODVictim->HintMessage( HINT_PLAYER_KILLED_WAVETIME );
  3277. // If we already played the killed hint, play the deathcam hint
  3278. //if ( !bPlayed )
  3279. //{
  3280. // pDODVictim->HintMessage( HINT_DEATHCAM );
  3281. //}
  3282. CBaseEntity *pInflictor = info.GetInflictor();
  3283. CBaseEntity *pKiller = info.GetAttacker();
  3284. CDODPlayer *pScorer = ToDODPlayer( GetDeathScorer( pKiller, pInflictor ) );
  3285. if( pScorer && pScorer->IsPlayer() && pScorer != pVictim )
  3286. {
  3287. if( pVictim->GetTeamNumber() == pScorer->GetTeamNumber() )
  3288. {
  3289. pScorer->HintMessage( HINT_FRIEND_KILLED, true ); //force this
  3290. }
  3291. else
  3292. {
  3293. pScorer->HintMessage( HINT_ENEMY_KILLED );
  3294. }
  3295. }
  3296. // determine if this kill affected a nemesis relationship
  3297. int iDeathFlags = 0;
  3298. if ( pScorer )
  3299. {
  3300. CalcDominationAndRevenge( pScorer, pDODVictim, &iDeathFlags );
  3301. }
  3302. pDODVictim->SetDeathFlags( iDeathFlags ); // for deathnotice I assume?
  3303. DeathNotice( pVictim, info );
  3304. // dvsents2: uncomment when removing all FireTargets
  3305. // variant_t value;
  3306. // g_EventQueue.AddEvent( "game_playerdie", "Use", value, 0, pVictim, pVictim );
  3307. FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 );
  3308. bool bScoring = !IsInWarmup();
  3309. if( bScoring )
  3310. {
  3311. pVictim->IncrementDeathCount( 1 );
  3312. }
  3313. // Did the player kill himself?
  3314. if ( pVictim == pScorer )
  3315. {
  3316. // Players lose a frag for killing themselves
  3317. //if( bScoring )
  3318. // pVictim->IncrementFragCount( -1 );
  3319. }
  3320. else if ( pScorer )
  3321. {
  3322. // if a player dies in a deathmatch game and the killer is a client, award the killer some points
  3323. if( bScoring )
  3324. pScorer->IncrementFragCount( DODPointsForKill( pVictim, info ) );
  3325. // Allow the scorer to immediately paint a decal
  3326. pScorer->AllowImmediateDecalPainting();
  3327. // dvsents2: uncomment when removing all FireTargets
  3328. //variant_t value;
  3329. //g_EventQueue.AddEvent( "game_playerkill", "Use", value, 0, pScorer, pScorer );
  3330. FireTargets( "game_playerkill", pScorer, pScorer, USE_TOGGLE, 0 );
  3331. // see if this saved a capture
  3332. if ( pDODVictim->m_signals.GetState() & SIGNAL_CAPTUREAREA )
  3333. {
  3334. //find the area the player is in and see if his death causes a block
  3335. CAreaCapture *pArea = dynamic_cast<CAreaCapture *>(gEntList.FindEntityByClassname( NULL, "dod_capture_area" ) );
  3336. while( pArea )
  3337. {
  3338. if ( pArea->CheckIfDeathCausesBlock( pDODVictim, pScorer ) )
  3339. {
  3340. break;
  3341. }
  3342. pArea = dynamic_cast<CAreaCapture *>( gEntList.FindEntityByClassname( pArea, "dod_capture_area" ) );
  3343. }
  3344. }
  3345. if ( pDODVictim->m_bIsDefusing && pDODVictim->m_pDefuseTarget && pScorer->GetTeamNumber() != pDODVictim->GetTeamNumber() )
  3346. {
  3347. CDODBombTarget *pTarget = pDODVictim->m_pDefuseTarget;
  3348. pTarget->DefuseBlocked( pScorer );
  3349. IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_defuser" );
  3350. if ( event )
  3351. {
  3352. event->SetInt( "userid", pScorer->GetUserID() );
  3353. event->SetInt( "victimid", pDODVictim->GetUserID() );
  3354. gameeventmanager->FireEvent( event );
  3355. }
  3356. }
  3357. }
  3358. else
  3359. {
  3360. // Players lose a frag for letting the world kill them
  3361. //if( bScoring )
  3362. // pVictim->IncrementFragCount( -1 );
  3363. }
  3364. }
  3365. void CDODGameRules::DetectGameRules( void )
  3366. {
  3367. bool bFound = false;
  3368. CBaseEntity *pEnt = NULL;
  3369. pEnt = gEntList.FindEntityByClassname( pEnt, "info_doddetect" );
  3370. while( pEnt )
  3371. {
  3372. CDODDetect *pDetect = dynamic_cast<CDODDetect *>(pEnt);
  3373. if( pDetect && pDetect->IsMasteredOn() )
  3374. {
  3375. CDODGamePlayRules *pRules = pDetect->GetGamePlay();
  3376. Assert( pRules );
  3377. CopyGamePlayLogic( *pRules );
  3378. bFound = true;
  3379. break;
  3380. }
  3381. pEnt = gEntList.FindEntityByClassname( pEnt, "info_doddetect" );
  3382. }
  3383. if( !bFound )
  3384. {
  3385. m_GamePlayRules.Reset();
  3386. }
  3387. }
  3388. void CDODGameRules::PlayWinSong( int team )
  3389. {
  3390. switch(team)
  3391. {
  3392. case TEAM_ALLIES:
  3393. BroadcastSound( "Game.USWin" );
  3394. break;
  3395. case TEAM_AXIS:
  3396. BroadcastSound( "Game.GermanWin" );
  3397. break;
  3398. default:
  3399. Assert(0);
  3400. break;
  3401. }
  3402. }
  3403. void CDODGameRules::BroadcastSound( const char *sound )
  3404. {
  3405. //send it to everyone
  3406. IGameEvent *event = gameeventmanager->CreateEvent( "dod_broadcast_audio" );
  3407. if ( event )
  3408. {
  3409. event->SetString( "sound", sound );
  3410. gameeventmanager->FireEvent( event );
  3411. }
  3412. }
  3413. void CDODGameRules::PlayStartRoundVoice( void )
  3414. {
  3415. // One for the Allies..
  3416. switch( m_GamePlayRules.m_iAlliesStartRoundVoice )
  3417. {
  3418. case STARTROUND_ATTACK:
  3419. PlaySpawnSoundToTeam( "Voice.US_ObjectivesAttack", TEAM_ALLIES );
  3420. break;
  3421. case STARTROUND_DEFEND:
  3422. PlaySpawnSoundToTeam( "Voice.US_ObjectivesDefend", TEAM_ALLIES );
  3423. break;
  3424. case STARTROUND_BEACH:
  3425. PlaySpawnSoundToTeam( "Voice.US_Beach", TEAM_ALLIES );
  3426. break;
  3427. case STARTROUND_ATTACK_TIMED:
  3428. PlaySpawnSoundToTeam( "Voice.US_ObjectivesAttackTimed", TEAM_ALLIES );
  3429. break;
  3430. case STARTROUND_DEFEND_TIMED:
  3431. PlaySpawnSoundToTeam( "Voice.US_ObjectivesDefendTimed", TEAM_ALLIES );
  3432. break;
  3433. case STARTROUND_FLAGS:
  3434. default:
  3435. PlaySpawnSoundToTeam( "Voice.US_Flags", TEAM_ALLIES );
  3436. break;
  3437. }
  3438. // and one for the Axis
  3439. switch( m_GamePlayRules.m_iAxisStartRoundVoice )
  3440. {
  3441. case STARTROUND_ATTACK:
  3442. PlaySpawnSoundToTeam( "Voice.German_ObjectivesAttack", TEAM_AXIS );
  3443. break;
  3444. case STARTROUND_DEFEND:
  3445. PlaySpawnSoundToTeam( "Voice.German_ObjectivesDefend", TEAM_AXIS );
  3446. break;
  3447. case STARTROUND_BEACH:
  3448. PlaySpawnSoundToTeam( "Voice.German_Beach", TEAM_AXIS );
  3449. break;
  3450. case STARTROUND_ATTACK_TIMED:
  3451. PlaySpawnSoundToTeam( "Voice.German_ObjectivesAttackTimed", TEAM_AXIS );
  3452. break;
  3453. case STARTROUND_DEFEND_TIMED:
  3454. PlaySpawnSoundToTeam( "Voice.German_ObjectivesDefendTimed", TEAM_AXIS );
  3455. break;
  3456. case STARTROUND_FLAGS:
  3457. default:
  3458. PlaySpawnSoundToTeam( "Voice.German_Flags", TEAM_AXIS );
  3459. break;
  3460. }
  3461. }
  3462. void CDODGameRules::PlaySpawnSoundToTeam( const char *sound, int team )
  3463. {
  3464. // find the first valid player and make them do it as a voice command
  3465. CDODPlayer *pPlayer;
  3466. static int iLastSpeaker = 1;
  3467. int iCurrent = iLastSpeaker;
  3468. bool bBreakLoop = false;
  3469. while( !bBreakLoop )
  3470. {
  3471. iCurrent++;
  3472. if( iCurrent > gpGlobals->maxClients )
  3473. iCurrent = 1;
  3474. if( iCurrent == iLastSpeaker )
  3475. {
  3476. // couldn't find a different player. check the same player again
  3477. // and then break regardless
  3478. bBreakLoop = true;
  3479. }
  3480. pPlayer = ToDODPlayer( UTIL_PlayerByIndex( iCurrent ) );
  3481. if (pPlayer == NULL)
  3482. continue;
  3483. if (FNullEnt( pPlayer->edict() ))
  3484. continue;
  3485. if( pPlayer && pPlayer->GetTeamNumber() == team && pPlayer->IsAlive() )
  3486. {
  3487. CPASFilter filter( pPlayer->WorldSpaceCenter() );
  3488. pPlayer->EmitSound( filter, pPlayer->entindex(), sound );
  3489. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_HANDSIGNAL );
  3490. iLastSpeaker = iCurrent;
  3491. break;
  3492. }
  3493. }
  3494. }
  3495. void CDODGameRules::ClientDisconnected( edict_t *pClient )
  3496. {
  3497. CDODPlayer *pPlayer = ToDODPlayer( GetContainingEntity( pClient ) );
  3498. if( pPlayer )
  3499. {
  3500. pPlayer->DestroyRagdoll();
  3501. pPlayer->StatEvent_UploadStats();
  3502. }
  3503. // Tally the latest time for this player
  3504. pPlayer->TallyLatestTimePlayedPerClass( pPlayer->GetTeamNumber(), pPlayer->m_Shared.DesiredPlayerClass() );
  3505. pPlayer->RemoveNemesisRelationships();
  3506. for( int j=0;j<7;j++ )
  3507. {
  3508. m_flSecondsPlayedPerClass_Allies[j] += pPlayer->m_flTimePlayedPerClass_Allies[j];
  3509. m_flSecondsPlayedPerClass_Axis[j] += pPlayer->m_flTimePlayedPerClass_Axis[j];
  3510. }
  3511. int iPlayerIndex = pPlayer->entindex();
  3512. Assert( iPlayerIndex >= 1 && iPlayerIndex <= MAX_PLAYERS);
  3513. if ( iPlayerIndex >= 1 && iPlayerIndex <= MAX_PLAYERS )
  3514. {
  3515. // for every other player, set all all the kills with respect to this player to 0
  3516. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  3517. {
  3518. CDODPlayer *p = ToDODPlayer( UTIL_PlayerByIndex(i) );
  3519. if ( !p )
  3520. continue;
  3521. p->iNumKilledByUnanswered[iPlayerIndex] = 0;
  3522. }
  3523. }
  3524. BaseClass::ClientDisconnected( pClient );
  3525. }
  3526. void CDODGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info )
  3527. {
  3528. // Work out what killed the player, and send a message to all clients about it
  3529. const char *killer_weapon_name = "world"; // by default, the player is killed by the world
  3530. int killer_ID = 0;
  3531. // Find the killer & the scorer
  3532. CBaseEntity *pInflictor = info.GetInflictor();
  3533. CBaseEntity *pKiller = info.GetAttacker();
  3534. CDODPlayer *pScorer = ToDODPlayer( GetDeathScorer( pKiller, pInflictor ) );
  3535. CDODPlayer *pDODVictim = ToDODPlayer( pVictim );
  3536. Assert( pDODVictim );
  3537. if ( pScorer ) // Is the killer a client?
  3538. {
  3539. killer_ID = pScorer->GetUserID();
  3540. if ( pInflictor )
  3541. {
  3542. if ( pInflictor == pScorer )
  3543. {
  3544. CWeaponDODBase *pWeapon = pScorer->GetActiveDODWeapon();
  3545. if ( pWeapon )
  3546. {
  3547. int iWeaponType = pWeapon->GetDODWpnData().m_WeaponType;
  3548. // Putting this here because we already have the weapon pointer.
  3549. if ( iWeaponType == WPN_TYPE_MG && pScorer->GetTeamNumber() != pVictim->GetTeamNumber() )
  3550. {
  3551. CDODBipodWeapon *pMG = dynamic_cast<CDODBipodWeapon *>( pWeapon );
  3552. Assert( pMG );
  3553. if ( pMG->IsDeployed() )
  3554. {
  3555. pScorer->HandleDeployedMGKillCount( 1 );
  3556. }
  3557. }
  3558. // if the weapon does not belong to the same team
  3559. if ( pWeapon->GetDODWpnData().m_iDefaultTeam != pScorer->GetTeamNumber() &&
  3560. pScorer->GetTeamNumber() != pVictim->GetTeamNumber() )
  3561. {
  3562. pScorer->HandleEnemyWeaponsAchievement( 1 );
  3563. }
  3564. // achievement for getting kills with several different weapon types in one life
  3565. if ( pScorer->GetTeamNumber() != pVictim->GetTeamNumber() )
  3566. {
  3567. pScorer->HandleComboWeaponKill( iWeaponType );
  3568. }
  3569. if( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK )
  3570. {
  3571. //it was a butt or bayonet!
  3572. killer_weapon_name = pWeapon->GetSecondaryDeathNoticeName();
  3573. }
  3574. // If the inflictor is the killer, then it must be their current weapon doing the damage
  3575. else
  3576. {
  3577. killer_weapon_name = pWeapon->GetClassname();
  3578. }
  3579. }
  3580. }
  3581. else
  3582. {
  3583. killer_weapon_name = STRING( pInflictor->m_iClassname ); // it's just that easy
  3584. }
  3585. }
  3586. }
  3587. else
  3588. {
  3589. killer_weapon_name = STRING( pInflictor->m_iClassname );
  3590. }
  3591. // strip the NPC_* or weapon_* from the inflictor's classname
  3592. if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 )
  3593. {
  3594. killer_weapon_name += 7;
  3595. }
  3596. else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 )
  3597. {
  3598. killer_weapon_name += 5;
  3599. }
  3600. else if ( strncmp( killer_weapon_name, "rocket_", 7 ) == 0 )
  3601. {
  3602. killer_weapon_name += 7;
  3603. }
  3604. else if ( strncmp( killer_weapon_name, "grenade_", 8 ) == 0 )
  3605. {
  3606. killer_weapon_name += 8;
  3607. // achievement for getting kills with several different weapon types in one life
  3608. if ( pScorer && pScorer->GetTeamNumber() != pVictim->GetTeamNumber() )
  3609. {
  3610. pScorer->HandleComboWeaponKill( WPN_TYPE_GRENADE );
  3611. }
  3612. }
  3613. IGameEvent *event = gameeventmanager->CreateEvent( "player_death" );
  3614. if ( event )
  3615. {
  3616. event->SetInt("userid", pVictim->GetUserID() );
  3617. event->SetInt("attacker", killer_ID );
  3618. event->SetString("weapon", killer_weapon_name );
  3619. event->SetInt("priority", 7 );
  3620. if ( pDODVictim->GetDeathFlags() & DOD_DEATHFLAG_DOMINATION )
  3621. {
  3622. event->SetInt( "dominated", 1 );
  3623. }
  3624. if ( pDODVictim->GetDeathFlags() & DOD_DEATHFLAG_REVENGE )
  3625. {
  3626. event->SetInt( "revenge", 1 );
  3627. }
  3628. gameeventmanager->FireEvent( event );
  3629. }
  3630. }
  3631. // CDODDetect - map entity for mappers to choose game rules
  3632. LINK_ENTITY_TO_CLASS( info_doddetect, CDODDetect );
  3633. CDODDetect::CDODDetect()
  3634. {
  3635. m_GamePlayRules.Reset();
  3636. }
  3637. void CDODDetect::Spawn( void )
  3638. {
  3639. SetSolid( SOLID_NONE );
  3640. BaseClass::Spawn();
  3641. }
  3642. bool CDODDetect::IsMasteredOn( void )
  3643. {
  3644. //For now return true
  3645. return true;
  3646. }
  3647. bool CDODDetect::KeyValue( const char *szKeyName, const char *szValue )
  3648. {
  3649. if (FStrEq(szKeyName, "detect_allies_respawnfactor"))
  3650. {
  3651. m_GamePlayRules.m_fAlliesRespawnFactor = atof(szValue);
  3652. }
  3653. else if (FStrEq(szKeyName, "detect_axis_respawnfactor"))
  3654. {
  3655. m_GamePlayRules.m_fAxisRespawnFactor = atof(szValue);
  3656. }
  3657. else if (FStrEq(szKeyName, "detect_allies_startroundvoice"))
  3658. {
  3659. m_GamePlayRules.m_iAlliesStartRoundVoice = atoi(szValue);
  3660. }
  3661. else if (FStrEq(szKeyName, "detect_axis_startroundvoice"))
  3662. {
  3663. m_GamePlayRules.m_iAxisStartRoundVoice = atof(szValue);
  3664. }
  3665. else
  3666. return CBaseEntity::KeyValue( szKeyName, szValue );
  3667. return true;
  3668. }
  3669. //checks to see if the desired team is stacked, returns true if it is
  3670. bool CDODGameRules::TeamStacked( int iNewTeam, int iCurTeam )
  3671. {
  3672. //players are allowed to change to their own team
  3673. if(iNewTeam == iCurTeam)
  3674. return false;
  3675. int iTeamLimit = mp_limitteams.GetInt();
  3676. // Tabulate the number of players on each team.
  3677. int iNumAllies = GetGlobalTeam( TEAM_ALLIES )->GetNumPlayers();
  3678. int iNumAxis = GetGlobalTeam( TEAM_AXIS )->GetNumPlayers();
  3679. switch ( iNewTeam )
  3680. {
  3681. case TEAM_ALLIES:
  3682. if( iCurTeam != TEAM_UNASSIGNED && iCurTeam != TEAM_SPECTATOR )
  3683. {
  3684. if((iNumAllies + 1) > (iNumAxis + iTeamLimit - 1))
  3685. return true;
  3686. else
  3687. return false;
  3688. }
  3689. else
  3690. {
  3691. if((iNumAllies + 1) > (iNumAxis + iTeamLimit))
  3692. return true;
  3693. else
  3694. return false;
  3695. }
  3696. break;
  3697. case TEAM_AXIS:
  3698. if( iCurTeam != TEAM_UNASSIGNED && iCurTeam != TEAM_SPECTATOR )
  3699. {
  3700. if((iNumAxis + 1) > (iNumAllies + iTeamLimit - 1))
  3701. return true;
  3702. else
  3703. return false;
  3704. }
  3705. else
  3706. {
  3707. if((iNumAxis + 1) > (iNumAllies + iTeamLimit))
  3708. return true;
  3709. else
  3710. return false;
  3711. }
  3712. break;
  3713. }
  3714. return false;
  3715. }
  3716. // Falling damage stuff.
  3717. #define DOD_PLAYER_FATAL_FALL_SPEED 900 // approx 60 feet
  3718. #define DOD_PLAYER_MAX_SAFE_FALL_SPEED 500 // approx 20 feet
  3719. #define DOD_DAMAGE_FOR_FALL_SPEED ((float)100 / ( DOD_PLAYER_FATAL_FALL_SPEED - DOD_PLAYER_MAX_SAFE_FALL_SPEED )) // damage per unit per second.
  3720. /*
  3721. #define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast.
  3722. */
  3723. float CDODGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer )
  3724. {
  3725. pPlayer->m_Local.m_flFallVelocity -= DOD_PLAYER_MAX_SAFE_FALL_SPEED;
  3726. return pPlayer->m_Local.m_flFallVelocity * DOD_DAMAGE_FOR_FALL_SPEED;
  3727. }
  3728. #endif
  3729. //-----------------------------------------------------------------------------
  3730. // Purpose: Init CS ammo definitions
  3731. //-----------------------------------------------------------------------------
  3732. // shared ammo definition
  3733. // JAY: Trying to make a more physical bullet response
  3734. #define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f)
  3735. #define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains))
  3736. // exaggerate all of the forces, but use real numbers to keep them consistent
  3737. #define BULLET_IMPULSE_EXAGGERATION 1
  3738. // convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s
  3739. #define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION)
  3740. CAmmoDef* GetAmmoDef()
  3741. {
  3742. static CAmmoDef def;
  3743. static bool bInitted = false;
  3744. if ( !bInitted )
  3745. {
  3746. bInitted = true;
  3747. //pistol ammo
  3748. def.AddAmmoType( DOD_AMMO_COLT, DMG_BULLET, TRACER_NONE, 0, 0, 21, 5000, 10, 14 );
  3749. def.AddAmmoType( DOD_AMMO_P38, DMG_BULLET, TRACER_NONE, 0, 0, 24, 5000, 10, 14 );
  3750. def.AddAmmoType( DOD_AMMO_C96, DMG_BULLET, TRACER_NONE, 0, 0, 60, 5000, 10, 14 );
  3751. //rifles
  3752. def.AddAmmoType( DOD_AMMO_GARAND, DMG_BULLET, TRACER_NONE, 0, 0, 88, 9000, 10, 14 );
  3753. def.AddAmmoType( DOD_AMMO_K98, DMG_BULLET, TRACER_NONE, 0, 0, 65, 9000, 10, 14 );
  3754. def.AddAmmoType( DOD_AMMO_M1CARBINE, DMG_BULLET, TRACER_NONE, 0, 0, 165, 9000, 10, 14 );
  3755. def.AddAmmoType( DOD_AMMO_SPRING, DMG_BULLET, TRACER_NONE, 0, 0, 55, 9000, 10, 14 );
  3756. //submg
  3757. def.AddAmmoType( DOD_AMMO_SUBMG, DMG_BULLET, TRACER_NONE, 0, 0, 210, 7000, 10, 14 );
  3758. def.AddAmmoType( DOD_AMMO_BAR, DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 260, 9000, 10, 14 );
  3759. //mg
  3760. def.AddAmmoType( DOD_AMMO_30CAL, DMG_BULLET | DMG_MACHINEGUN, TRACER_LINE_AND_WHIZ, 0, 0, 300, 9000, 10, 14 );
  3761. def.AddAmmoType( DOD_AMMO_MG42, DMG_BULLET | DMG_MACHINEGUN, TRACER_LINE_AND_WHIZ, 0, 0, 500, 9000, 10, 14 );
  3762. //rockets
  3763. def.AddAmmoType( DOD_AMMO_ROCKET, DMG_BLAST, TRACER_NONE, 0, 0, 5, 9000, 10, 14 );
  3764. //grenades
  3765. def.AddAmmoType( DOD_AMMO_HANDGRENADE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3766. def.AddAmmoType( DOD_AMMO_STICKGRENADE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3767. def.AddAmmoType( DOD_AMMO_HANDGRENADE_EX, DMG_BLAST, TRACER_NONE, 0, 0, 1, 1, 4, 8 );
  3768. def.AddAmmoType( DOD_AMMO_STICKGRENADE_EX, DMG_BLAST, TRACER_NONE, 0, 0, 1, 1, 4, 8 );
  3769. // smoke grenades
  3770. def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_US, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3771. def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_GER, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3772. def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_US_LIVE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3773. def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_GER_LIVE,DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3774. // rifle grenades
  3775. def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_US, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3776. def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_GER, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3777. def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_US_LIVE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3778. def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_GER_LIVE,DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 );
  3779. }
  3780. return &def;
  3781. }
  3782. #ifndef CLIENT_DLL
  3783. void CDODGameRules::AddWaveTime( int team, float flTime )
  3784. {
  3785. switch ( team )
  3786. {
  3787. case TEAM_ALLIES:
  3788. {
  3789. Assert( m_iNumAlliesRespawnWaves < DOD_RESPAWN_QUEUE_SIZE );
  3790. if ( m_iNumAlliesRespawnWaves >= DOD_RESPAWN_QUEUE_SIZE )
  3791. {
  3792. Warning( "Trying to add too many allies respawn waves\n" );
  3793. return;
  3794. }
  3795. m_AlliesRespawnQueue.Set( m_iAlliesRespawnTail, gpGlobals->curtime + flTime );
  3796. m_iNumAlliesRespawnWaves++;
  3797. m_iAlliesRespawnTail = ( m_iAlliesRespawnTail + 1 ) % DOD_RESPAWN_QUEUE_SIZE;
  3798. DevMsg( 1, "AddWaveTime ALLIES head %d tail %d numtotal %d time %.1f\n",
  3799. m_iAlliesRespawnHead.Get(),
  3800. m_iAlliesRespawnTail.Get(),
  3801. m_iNumAlliesRespawnWaves,
  3802. gpGlobals->curtime + flTime );
  3803. }
  3804. break;
  3805. case TEAM_AXIS:
  3806. {
  3807. Assert( m_iNumAxisRespawnWaves < DOD_RESPAWN_QUEUE_SIZE );
  3808. if ( m_iNumAxisRespawnWaves >= DOD_RESPAWN_QUEUE_SIZE )
  3809. {
  3810. Warning( "Trying to add too many axis respawn waves\n" );
  3811. return;
  3812. }
  3813. m_AxisRespawnQueue.Set( m_iAxisRespawnTail, gpGlobals->curtime + flTime );
  3814. m_iNumAxisRespawnWaves++;
  3815. m_iAxisRespawnTail = ( m_iAxisRespawnTail + 1 ) % DOD_RESPAWN_QUEUE_SIZE;
  3816. DevMsg( 1, "AddWaveTime AXIS head %d tail %d numtotal %d time %.1f\n",
  3817. m_iAxisRespawnHead.Get(),
  3818. m_iAxisRespawnTail.Get(),
  3819. m_iNumAxisRespawnWaves,
  3820. gpGlobals->curtime + flTime );
  3821. }
  3822. break;
  3823. default:
  3824. Assert(0);
  3825. break;
  3826. }
  3827. }
  3828. void CDODGameRules::PopWaveTime( int team )
  3829. {
  3830. switch ( team )
  3831. {
  3832. case TEAM_ALLIES:
  3833. {
  3834. Assert( m_iNumAlliesRespawnWaves > 0 );
  3835. m_iAlliesRespawnHead = ( m_iAlliesRespawnHead + 1 ) % DOD_RESPAWN_QUEUE_SIZE;
  3836. m_iNumAlliesRespawnWaves--;
  3837. DevMsg( 1, "PopWaveTime ALLIES head %d tail %d numtotal %d time %.1f\n",
  3838. m_iAlliesRespawnHead.Get(),
  3839. m_iAlliesRespawnTail.Get(),
  3840. m_iNumAlliesRespawnWaves,
  3841. gpGlobals->curtime );
  3842. }
  3843. break;
  3844. case TEAM_AXIS:
  3845. {
  3846. Assert( m_iNumAxisRespawnWaves > 0 );
  3847. m_iAxisRespawnHead = ( m_iAxisRespawnHead + 1 ) % DOD_RESPAWN_QUEUE_SIZE;
  3848. m_iNumAxisRespawnWaves--;
  3849. DevMsg( 1, "PopWaveTime AXIS head %d tail %d numtotal %d time %.1f\n",
  3850. m_iAxisRespawnHead.Get(),
  3851. m_iAxisRespawnTail.Get(),
  3852. m_iNumAxisRespawnWaves,
  3853. gpGlobals->curtime );
  3854. }
  3855. break;
  3856. default:
  3857. Assert(0);
  3858. break;
  3859. }
  3860. }
  3861. #endif
  3862. #ifndef CLIENT_DLL
  3863. const char *CDODGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer )
  3864. {
  3865. char *pszPrefix = "";
  3866. if ( !pPlayer ) // dedicated server output
  3867. {
  3868. pszPrefix = "";
  3869. }
  3870. else
  3871. {
  3872. // don't show dead prefix if in the bonus round or at round end
  3873. // because we can chat at these times.
  3874. bool bShowDeadPrefix = ( pPlayer->IsAlive() == false ) && !IsInBonusRound() &&
  3875. ( State_Get() != STATE_GAME_OVER );
  3876. if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
  3877. {
  3878. return "";
  3879. }
  3880. if ( bTeamOnly )
  3881. {
  3882. if ( bShowDeadPrefix )
  3883. {
  3884. pszPrefix = "(Dead)(Team)"; //#chatprefix_deadteam";
  3885. }
  3886. else
  3887. {
  3888. //MATTTODO: localize chat prefixes
  3889. pszPrefix = "(Team)"; //"#chatprefix_team";
  3890. }
  3891. }
  3892. // everyone
  3893. else
  3894. {
  3895. if ( bShowDeadPrefix )
  3896. {
  3897. pszPrefix = "(Dead)"; //"#chatprefix_dead";
  3898. }
  3899. }
  3900. }
  3901. return pszPrefix;
  3902. }
  3903. void CDODGameRules::ClientSettingsChanged( CBasePlayer *pPlayer )
  3904. {
  3905. CDODPlayer *pDODPlayer = ToDODPlayer( pPlayer );
  3906. Assert( pDODPlayer );
  3907. pDODPlayer->SetAutoReload( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autoreload" ) ) > 0 );
  3908. pDODPlayer->SetShowHints( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_showhelp" ) ) > 0 );
  3909. pDODPlayer->SetAutoRezoom( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autorezoom" ) ) > 0 );
  3910. BaseClass::ClientSettingsChanged( pPlayer );
  3911. }
  3912. //-----------------------------------------------------------------------------
  3913. // Purpose: Determines if attacker and victim have gotten domination or revenge
  3914. //-----------------------------------------------------------------------------
  3915. void CDODGameRules::CalcDominationAndRevenge( CDODPlayer *pAttacker, CDODPlayer *pVictim, int *piDeathFlags )
  3916. {
  3917. // team kills don't count
  3918. if ( pAttacker->GetTeamNumber() == pVictim->GetTeamNumber() )
  3919. return;
  3920. int iKillsUnanswered = ++(pVictim->iNumKilledByUnanswered[pAttacker->entindex()]);
  3921. pAttacker->iNumKilledByUnanswered[pVictim->entindex()] = 0;
  3922. if ( DOD_KILLS_DOMINATION == iKillsUnanswered )
  3923. {
  3924. // this is the Nth unanswered kill between killer and victim, killer is now dominating victim
  3925. *piDeathFlags |= DOD_DEATHFLAG_DOMINATION;
  3926. // set victim to be dominated by killer
  3927. pAttacker->m_Shared.SetPlayerDominated( pVictim, true );
  3928. pAttacker->StatEvent_ScoredDomination();
  3929. }
  3930. else if ( pVictim->m_Shared.IsPlayerDominated( pAttacker->entindex() ) )
  3931. {
  3932. // the killer killed someone who was dominating him, gains revenge
  3933. *piDeathFlags |= DOD_DEATHFLAG_REVENGE;
  3934. // set victim to no longer be dominating the killer
  3935. pVictim->m_Shared.SetPlayerDominated( pAttacker, false );
  3936. pAttacker->StatEvent_ScoredRevenge();
  3937. }
  3938. }
  3939. int CDODGameRules::DODPointsForKill( CBasePlayer *pVictim, const CTakeDamageInfo &info )
  3940. {
  3941. if ( IsInWarmup() )
  3942. return 0;
  3943. CBaseEntity *pInflictor = info.GetInflictor();
  3944. CBaseEntity *pKiller = info.GetAttacker();
  3945. CDODPlayer *pScorer = ToDODPlayer( GetDeathScorer( pKiller, pInflictor ) );
  3946. // Don't give -1 points for killing a teammate with the bomb.
  3947. // It was their fault for standing too close really.
  3948. if ( pVictim->GetTeamNumber() == pScorer->GetTeamNumber() &&
  3949. info.GetDamageType() & DMG_BOMB )
  3950. {
  3951. return 0;
  3952. }
  3953. return BaseClass::IPointsForKill( pScorer, pVictim );
  3954. }
  3955. //-----------------------------------------------------------------------------
  3956. // Purpose: Returns the weapon in the player's inventory that would be better than
  3957. // the given weapon.
  3958. // Note, this version allows us to switch to a weapon that has no ammo as a last
  3959. // resort.
  3960. //-----------------------------------------------------------------------------
  3961. CBaseCombatWeapon *CDODGameRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon )
  3962. {
  3963. CBaseCombatWeapon *pCheck;
  3964. CBaseCombatWeapon *pBest;// this will be used in the event that we don't find a weapon in the same category.
  3965. int iCurrentWeight = -1;
  3966. int iBestWeight = -1;// no weapon lower than -1 can be autoswitched to
  3967. pBest = NULL;
  3968. // If I have a weapon, make sure I'm allowed to holster it
  3969. if ( pCurrentWeapon )
  3970. {
  3971. if ( !pCurrentWeapon->AllowsAutoSwitchFrom() || !pCurrentWeapon->CanHolster() )
  3972. {
  3973. // Either this weapon doesn't allow autoswitching away from it or I
  3974. // can't put this weapon away right now, so I can't switch.
  3975. return NULL;
  3976. }
  3977. iCurrentWeight = pCurrentWeapon->GetWeight();
  3978. }
  3979. for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i )
  3980. {
  3981. pCheck = pPlayer->GetWeapon( i );
  3982. if ( !pCheck )
  3983. continue;
  3984. // If we have an active weapon and this weapon doesn't allow autoswitching away
  3985. // from another weapon, skip it.
  3986. if ( pCurrentWeapon && !pCheck->AllowsAutoSwitchTo() )
  3987. continue;
  3988. int iWeight = pCheck->GetWeight();
  3989. // Empty weapons are lowest priority
  3990. if ( !pCheck->HasAnyAmmo() )
  3991. {
  3992. iWeight = 0;
  3993. }
  3994. if ( iWeight > -1 && iWeight == iCurrentWeight && pCheck != pCurrentWeapon )
  3995. {
  3996. // this weapon is from the same category.
  3997. if ( pPlayer->Weapon_CanSwitchTo( pCheck ) )
  3998. {
  3999. return pCheck;
  4000. }
  4001. }
  4002. else if ( iWeight > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of
  4003. {
  4004. //Msg( "Considering %s\n", STRING( pCheck->GetClassname() );
  4005. // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight
  4006. // that the player was using. This will end up leaving the player with his heaviest-weighted
  4007. // weapon.
  4008. // if this weapon is useable, flag it as the best
  4009. iBestWeight = pCheck->GetWeight();
  4010. pBest = pCheck;
  4011. }
  4012. }
  4013. // if we make it here, we've checked all the weapons and found no useable
  4014. // weapon in the same catagory as the current weapon.
  4015. // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always
  4016. // at least get the crowbar, but ya never know.
  4017. return pBest;
  4018. }
  4019. char *szHitgroupNames[] =
  4020. {
  4021. "generic",
  4022. "head",
  4023. "chest",
  4024. "stomach",
  4025. "arm_left",
  4026. "arm_right",
  4027. "leg_left",
  4028. "leg_right"
  4029. };
  4030. void CDODGameRules::WriteStatsFile( const char *pszLogName )
  4031. {
  4032. int i, j, k;
  4033. FileHandle_t hFile = filesystem->Open( pszLogName, "w" );
  4034. if ( hFile == FILESYSTEM_INVALID_HANDLE )
  4035. {
  4036. Warning( "Helper_LoadFile: missing %s\n", pszLogName );
  4037. return;
  4038. }
  4039. // Header
  4040. filesystem->FPrintf( hFile, "<?xml version=\"1.0\" ?>\n\n" );
  4041. // open stats
  4042. filesystem->FPrintf( hFile, "<stats>\n" );
  4043. // per player
  4044. for ( i=0;i<MAX_PLAYERS;i++ )
  4045. {
  4046. CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  4047. if ( pPlayer )
  4048. {
  4049. filesystem->FPrintf( hFile, "\t<player>\n" );
  4050. filesystem->FPrintf( hFile, "\t\t<playername>%s</playername>", pPlayer->GetPlayerName() );
  4051. filesystem->FPrintf( hFile, "\t\t<steamid>STEAM:0:01</steamid>" );
  4052. //float flTimePlayed = gpGlobals->curtime - pPlayer->m_flConnectTime;
  4053. //filesystem->FPrintf( hFile, "\t\t<time_played>%.1f</time_played>\n", flTimePlayed );
  4054. /*
  4055. pPlayer->TallyLatestTimePlayedPerClass( pPlayer->GetTeamNumber(), pPlayer->m_Shared.DesiredPlayerClass() );
  4056. filesystem->FPrintf( hFile, "\t\t<time_played_per_class>\n" );
  4057. for( j=0;j<7;j++ )
  4058. {
  4059. // TODO : add real class names
  4060. filesystem->FPrintf( hFile, "\t\t\t<class%i>%.1f</class%i>\n",
  4061. j,
  4062. pPlayer->m_flTimePlayedPerClass[j],
  4063. j );
  4064. }
  4065. filesystem->FPrintf( hFile, "\t\t</time_played_per_class>\n" );
  4066. */
  4067. filesystem->FPrintf( hFile, "\t\t<area_captures>%i</area_captures>\n", pPlayer->m_iNumAreaCaptures );
  4068. filesystem->FPrintf( hFile, "\t\t<area_defenses>%i</area_defenses>\n", pPlayer->m_iNumAreaDefenses );
  4069. filesystem->FPrintf( hFile, "\t\t<bonus_round_kills>%i</bonus_round_kills>\n", pPlayer->m_iNumBonusRoundKills );
  4070. for ( j=0;j<MAX_WEAPONS;j++ )
  4071. {
  4072. if ( pPlayer->m_WeaponStats[j].m_iNumShotsTaken > 0 )
  4073. {
  4074. filesystem->FPrintf( hFile, "\t\t<weapon>\n" );
  4075. // weapon id
  4076. // weapon name
  4077. filesystem->FPrintf( hFile, "\t\t\t<weaponname>%s</weaponname>\n", WeaponIDToAlias( j ) );
  4078. filesystem->FPrintf( hFile, "\t\t\t<weaponid>%i</weaponid>\n", j );
  4079. filesystem->FPrintf( hFile, "\t\t\t<shots>%i</shots>\n", pPlayer->m_WeaponStats[j].m_iNumShotsTaken );
  4080. filesystem->FPrintf( hFile, "\t\t\t<hits>%i</hits>\n", pPlayer->m_WeaponStats[j].m_iNumShotsHit );
  4081. filesystem->FPrintf( hFile, "\t\t\t<damage>%i</damage>\n", pPlayer->m_WeaponStats[j].m_iTotalDamageGiven );
  4082. filesystem->FPrintf( hFile, "\t\t\t<avgdist>%.1f</avgdist>\n", pPlayer->m_WeaponStats[j].m_flAverageHitDistance );
  4083. filesystem->FPrintf( hFile, "\t\t\t<kills>%i</kills>\n", pPlayer->m_WeaponStats[j].m_iNumKills );
  4084. filesystem->FPrintf( hFile, "\t\t\t<hitgroups_hit>\n" );
  4085. for( k=0;k<8;k++ )
  4086. {
  4087. if ( pPlayer->m_WeaponStats[j].m_iBodygroupsHit[k] > 0 )
  4088. {
  4089. filesystem->FPrintf( hFile, "\t\t\t\t<%s>%i</%s>\n",
  4090. szHitgroupNames[k],
  4091. pPlayer->m_WeaponStats[j].m_iBodygroupsHit[k],
  4092. szHitgroupNames[k] );
  4093. }
  4094. }
  4095. filesystem->FPrintf( hFile, "\t\t\t</hitgroups_hit>\n" );
  4096. filesystem->FPrintf( hFile, "\t\t\t<times_hit>%i</times_hit>\n", pPlayer->m_WeaponStats[j].m_iNumHitsTaken );
  4097. filesystem->FPrintf( hFile, "\t\t\t<damage_taken>%i</damage_taken>\n", pPlayer->m_WeaponStats[j].m_iTotalDamageTaken );
  4098. filesystem->FPrintf( hFile, "\t\t\t<times_killed>%i</times_killed>\n", pPlayer->m_WeaponStats[j].m_iTimesKilled );
  4099. filesystem->FPrintf( hFile, "\t\t\t<hit_in_hitgroups>\n" );
  4100. for( k=0;k<8;k++ )
  4101. {
  4102. if ( pPlayer->m_WeaponStats[j].m_iHitInBodygroups[k] > 0 )
  4103. {
  4104. filesystem->FPrintf( hFile, "\t\t\t\t<%s>%i</%s>\n",
  4105. szHitgroupNames[k],
  4106. pPlayer->m_WeaponStats[j].m_iHitInBodygroups[k],
  4107. szHitgroupNames[k] );
  4108. }
  4109. }
  4110. filesystem->FPrintf( hFile, "\t\t\t</hit_in_hitgroups>\n" );
  4111. filesystem->FPrintf( hFile, "\t\t</weapon>\n" );
  4112. }
  4113. }
  4114. int numKilled = pPlayer->m_KilledPlayers.Count();
  4115. for ( j=0;j<numKilled;j++ )
  4116. {
  4117. filesystem->FPrintf( hFile, "<victim>\n" );
  4118. filesystem->FPrintf( hFile, "\t<name>%s</name>\n", pPlayer->m_KilledPlayers[j].m_szPlayerName );
  4119. filesystem->FPrintf( hFile, "\t<userid>%i</userid>\n", pPlayer->m_KilledPlayers[j].m_iUserID );
  4120. filesystem->FPrintf( hFile, "\t<kills>%i</kills>\n", pPlayer->m_KilledPlayers[j].m_iKills );
  4121. filesystem->FPrintf( hFile, "\t<damage>%i</damage>\n", pPlayer->m_KilledPlayers[j].m_iTotalDamage );
  4122. filesystem->FPrintf( hFile, "</victim>" );
  4123. }
  4124. int numAttackers = pPlayer->m_KilledByPlayers.Count();
  4125. for ( j=0;j<numAttackers;j++ )
  4126. {
  4127. filesystem->FPrintf( hFile, "<attacker>" );
  4128. filesystem->FPrintf( hFile, "\t<name>%s</name>\n", pPlayer->m_KilledByPlayers[j].m_szPlayerName );
  4129. filesystem->FPrintf( hFile, "\t<userid>%i</userid>\n", pPlayer->m_KilledByPlayers[j].m_iUserID );
  4130. filesystem->FPrintf( hFile, "\t<kills>%i</kills>\n", pPlayer->m_KilledByPlayers[j].m_iKills );
  4131. filesystem->FPrintf( hFile, "\t<damage>%i</damage>\n", pPlayer->m_KilledByPlayers[j].m_iTotalDamage );
  4132. filesystem->FPrintf( hFile, "</attacker>" );
  4133. }
  4134. filesystem->FPrintf( hFile, "\t</player>\n" );
  4135. }
  4136. }
  4137. // close stats
  4138. filesystem->FPrintf( hFile, "</stats>\n" );
  4139. filesystem->Close( hFile );
  4140. }
  4141. #include "dod_basegrenade.h"
  4142. //==========================================================
  4143. // Called on physics entities that the player +uses ( if sv_turbophysics is on )
  4144. // Here we want to exclude grenades
  4145. //==========================================================
  4146. bool CDODGameRules::CanEntityBeUsePushed( CBaseEntity *pEnt )
  4147. {
  4148. CDODBaseGrenade *pGrenade = dynamic_cast<CDODBaseGrenade *>( pEnt );
  4149. if ( pGrenade )
  4150. {
  4151. return false;
  4152. }
  4153. return true;
  4154. }
  4155. //-----------------------------------------------------------------------------
  4156. // Purpose: Engine asks for the list of convars that should tag the server
  4157. //-----------------------------------------------------------------------------
  4158. void CDODGameRules::GetTaggedConVarList( KeyValues *pCvarTagList )
  4159. {
  4160. BaseClass::GetTaggedConVarList( pCvarTagList );
  4161. KeyValues *pKV = new KeyValues( "tag" );
  4162. pKV->SetString( "convar", "mp_fadetoblack" );
  4163. pKV->SetString( "tag", "fadetoblack" );
  4164. pCvarTagList->AddSubKey( pKV );
  4165. }
  4166. #endif //indef CLIENT_DLL
  4167. #ifdef CLIENT_DLL
  4168. void CDODGameRules::SetRoundState( int iRoundState )
  4169. {
  4170. m_iRoundState = iRoundState;
  4171. m_flLastRoundStateChangeTime = gpGlobals->curtime;
  4172. }
  4173. #endif // CLIENT_DLL
  4174. bool CDODGameRules::IsBombingTeam( int team )
  4175. {
  4176. if ( team == TEAM_ALLIES )
  4177. return m_bAlliesAreBombing;
  4178. if ( team == TEAM_AXIS )
  4179. return m_bAxisAreBombing;
  4180. return false;
  4181. }
  4182. bool CDODGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer )
  4183. {
  4184. #ifdef GAME_DLL
  4185. if( pPlayer )
  4186. {
  4187. int iPlayerTeam = pPlayer->GetTeamNumber();
  4188. if( ( iPlayerTeam == TEAM_ALLIES ) || ( iPlayerTeam == TEAM_AXIS ) )
  4189. return false;
  4190. }
  4191. #else
  4192. int iLocalPlayerTeam = GetLocalPlayerTeam();
  4193. if( ( iLocalPlayerTeam == TEAM_ALLIES ) || ( iLocalPlayerTeam == TEAM_AXIS ) )
  4194. return false;
  4195. #endif
  4196. return true;
  4197. }
  4198. #ifndef CLIENT_DLL
  4199. ConVar dod_winter_never_drop_presents( "dod_winter_never_drop_presents", "0", FCVAR_CHEAT );
  4200. ConVar dod_winter_always_drop_presents( "dod_winter_always_drop_presents", "0", FCVAR_CHEAT );
  4201. ConVar dod_winter_present_drop_chance( "dod_winter_present_drop_chance", "0.2", FCVAR_CHEAT, "", true, 0.0, true, 1.0 );
  4202. float CDODGameRules::GetPresentDropChance( void )
  4203. {
  4204. if ( dod_winter_never_drop_presents.GetBool() )
  4205. {
  4206. return 0.0;
  4207. }
  4208. if ( dod_winter_always_drop_presents.GetBool() )
  4209. {
  4210. return 1.0;
  4211. }
  4212. if ( m_bWinterHolidayActive )
  4213. {
  4214. return dod_winter_present_drop_chance.GetFloat();
  4215. }
  4216. return 0.0;
  4217. }
  4218. #endif
  4219. //=========================
  4220. #ifndef CLIENT_DLL
  4221. class CFuncTeamWall : public CBaseEntity
  4222. {
  4223. DECLARE_DATADESC();
  4224. DECLARE_CLASS( CFuncTeamWall, CBaseEntity );
  4225. public:
  4226. virtual void Spawn();
  4227. virtual bool KeyValue( const char *szKeyName, const char *szValue ) ;
  4228. virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
  4229. void WallTouch( CBaseEntity *pOther );
  4230. void DrawThink( void );
  4231. private:
  4232. Vector m_vecMaxs;
  4233. Vector m_vecMins;
  4234. int m_iBlockTeam;
  4235. float m_flNextHintTime;
  4236. bool m_bShowWarning;
  4237. };
  4238. BEGIN_DATADESC( CFuncTeamWall )
  4239. DEFINE_KEYFIELD( m_iBlockTeam, FIELD_INTEGER, "blockteam" ),
  4240. DEFINE_KEYFIELD( m_vecMaxs, FIELD_VECTOR, "maxs" ),
  4241. DEFINE_KEYFIELD( m_vecMins, FIELD_VECTOR, "mins" ),
  4242. DEFINE_KEYFIELD( m_bShowWarning, FIELD_BOOLEAN, "warn" ),
  4243. DEFINE_THINKFUNC( DrawThink ),
  4244. DEFINE_FUNCTION( WallTouch ),
  4245. END_DATADESC()
  4246. LINK_ENTITY_TO_CLASS( func_team_wall, CFuncTeamWall );
  4247. ConCommand cc_Load_Blocker_Walls( "load_enttext", Load_EntText, 0, FCVAR_CHEAT );
  4248. ConVar showblockerwalls( "showblockerwalls", "0", FCVAR_CHEAT, "Set to 1 to visualize blocker walls" );
  4249. void CFuncTeamWall::Spawn( void )
  4250. {
  4251. SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything
  4252. SetModel( STRING( GetModelName() ) );
  4253. AddEffects( EF_NODRAW );
  4254. SetSolid( SOLID_BBOX );
  4255. // set our custom collision if we declared this ent through the .ent file
  4256. if ( m_vecMins != vec3_origin && m_vecMaxs != vec3_origin )
  4257. {
  4258. SetCollisionBounds( m_vecMins, m_vecMaxs );
  4259. // If we delcared an angle in the .ent file, make us OBB
  4260. if ( GetAbsAngles() != vec3_angle )
  4261. {
  4262. SetSolid( SOLID_OBB );
  4263. }
  4264. }
  4265. SetThink( &CFuncTeamWall::DrawThink );
  4266. SetNextThink( gpGlobals->curtime + 0.1 );
  4267. SetTouch( &CFuncTeamWall::WallTouch );
  4268. m_flNextHintTime = gpGlobals->curtime;
  4269. }
  4270. //-----------------------------------------------------------------------------
  4271. // Parse data from a map file
  4272. //-----------------------------------------------------------------------------
  4273. bool CFuncTeamWall::KeyValue( const char *szKeyName, const char *szValue )
  4274. {
  4275. if ( FStrEq( szKeyName, "mins" ))
  4276. {
  4277. UTIL_StringToVector( m_vecMins.Base(), szValue );
  4278. return true;
  4279. }
  4280. if ( FStrEq( szKeyName, "maxs" ))
  4281. {
  4282. UTIL_StringToVector( m_vecMaxs.Base(), szValue );
  4283. return true;
  4284. }
  4285. if ( FStrEq( szKeyName, "warn" ))
  4286. {
  4287. m_bShowWarning = atoi(szValue) > 0;
  4288. }
  4289. return BaseClass::KeyValue( szKeyName, szValue );
  4290. }
  4291. bool CFuncTeamWall::ShouldCollide( int collisionGroup, int contentsMask ) const
  4292. {
  4293. bool bShouldCollide = false;
  4294. if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
  4295. {
  4296. switch ( m_iBlockTeam )
  4297. {
  4298. case TEAM_UNASSIGNED:
  4299. bShouldCollide = ( contentsMask & ( CONTENTS_TEAM1 | CONTENTS_TEAM2 ) ) > 0;
  4300. break;
  4301. case TEAM_ALLIES:
  4302. bShouldCollide = ( contentsMask & CONTENTS_TEAM1 ) > 0;
  4303. break;
  4304. case TEAM_AXIS:
  4305. bShouldCollide = ( contentsMask & CONTENTS_TEAM2 ) > 0;
  4306. break;
  4307. default:
  4308. break;
  4309. }
  4310. }
  4311. return bShouldCollide;
  4312. }
  4313. void CFuncTeamWall::WallTouch( CBaseEntity *pOther )
  4314. {
  4315. if ( !m_bShowWarning )
  4316. return;
  4317. if ( pOther && pOther->IsPlayer() )
  4318. {
  4319. CDODPlayer *pPlayer = ToDODPlayer( pOther );
  4320. if ( pPlayer->GetTeamNumber() == m_iBlockTeam )
  4321. {
  4322. // show a "go away" icon
  4323. if ( m_flNextHintTime < gpGlobals->curtime )
  4324. {
  4325. pPlayer->HintMessage( "#dod_wrong_way" );
  4326. // global timer, but not critical to keep timer per player.
  4327. m_flNextHintTime = gpGlobals->curtime + 1.0;
  4328. }
  4329. }
  4330. }
  4331. }
  4332. void CFuncTeamWall::DrawThink( void )
  4333. {
  4334. if ( showblockerwalls.GetBool() )
  4335. {
  4336. NDebugOverlay::EntityBounds( this, 255, 0, 0, 0, 0.2 );
  4337. }
  4338. SetNextThink( gpGlobals->curtime + 0.1 );
  4339. }
  4340. #endif
  4341. #ifdef GAME_DLL
  4342. #include "modelentities.h"
  4343. #define SF_TEAM_WALL_NO_HINT (1<<1)
  4344. //-----------------------------------------------------------------------------
  4345. // Purpose: Visualizes a respawn room to the enemy team
  4346. //-----------------------------------------------------------------------------
  4347. class CFuncNewTeamWall : public CFuncBrush
  4348. {
  4349. DECLARE_CLASS( CFuncNewTeamWall, CFuncBrush );
  4350. public:
  4351. DECLARE_DATADESC();
  4352. DECLARE_SERVERCLASS();
  4353. virtual void Spawn( void );
  4354. virtual int UpdateTransmitState( void );
  4355. virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo );
  4356. virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
  4357. void WallTouch( CBaseEntity *pOther );
  4358. void SetActive( bool bActive );
  4359. private:
  4360. float m_flNextHintTime;
  4361. };
  4362. //===========================================================================================================
  4363. LINK_ENTITY_TO_CLASS( func_teamblocker, CFuncNewTeamWall );
  4364. BEGIN_DATADESC( CFuncNewTeamWall )
  4365. END_DATADESC()
  4366. IMPLEMENT_SERVERCLASS_ST( CFuncNewTeamWall, DT_FuncNewTeamWall )
  4367. END_SEND_TABLE()
  4368. //-----------------------------------------------------------------------------
  4369. // Purpose:
  4370. //-----------------------------------------------------------------------------
  4371. void CFuncNewTeamWall::Spawn( void )
  4372. {
  4373. BaseClass::Spawn();
  4374. SetActive( true );
  4375. SetCollisionGroup( DOD_COLLISIONGROUP_BLOCKERWALL );
  4376. if ( FBitSet( m_spawnflags, SF_TEAM_WALL_NO_HINT ) == false )
  4377. {
  4378. SetTouch( &CFuncNewTeamWall::WallTouch );
  4379. m_flNextHintTime = gpGlobals->curtime;
  4380. }
  4381. }
  4382. //-----------------------------------------------------------------------------
  4383. // Purpose:
  4384. //-----------------------------------------------------------------------------
  4385. int CFuncNewTeamWall::UpdateTransmitState()
  4386. {
  4387. return SetTransmitState( FL_EDICT_ALWAYS );
  4388. }
  4389. //-----------------------------------------------------------------------------
  4390. // Purpose: Only transmit this entity to clients that aren't in our team
  4391. //-----------------------------------------------------------------------------
  4392. int CFuncNewTeamWall::ShouldTransmit( const CCheckTransmitInfo *pInfo )
  4393. {
  4394. return FL_EDICT_ALWAYS;
  4395. }
  4396. //-----------------------------------------------------------------------------
  4397. // Purpose:
  4398. //-----------------------------------------------------------------------------
  4399. void CFuncNewTeamWall::SetActive( bool bActive )
  4400. {
  4401. if ( bActive )
  4402. {
  4403. // We're a trigger, but we want to be solid. Out ShouldCollide() will make
  4404. // us non-solid to members of the team that spawns here.
  4405. RemoveSolidFlags( FSOLID_TRIGGER );
  4406. RemoveSolidFlags( FSOLID_NOT_SOLID );
  4407. }
  4408. else
  4409. {
  4410. AddSolidFlags( FSOLID_NOT_SOLID );
  4411. AddSolidFlags( FSOLID_TRIGGER );
  4412. }
  4413. }
  4414. //-----------------------------------------------------------------------------
  4415. // Purpose:
  4416. //-----------------------------------------------------------------------------
  4417. bool CFuncNewTeamWall::ShouldCollide( int collisionGroup, int contentsMask ) const
  4418. {
  4419. if ( GetTeamNumber() == TEAM_UNASSIGNED )
  4420. return false;
  4421. if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
  4422. {
  4423. switch( GetTeamNumber() )
  4424. {
  4425. case TEAM_ALLIES:
  4426. if ( !(contentsMask & CONTENTS_TEAM1) )
  4427. return false;
  4428. break;
  4429. case TEAM_AXIS:
  4430. if ( !(contentsMask & CONTENTS_TEAM2) )
  4431. return false;
  4432. break;
  4433. }
  4434. return true;
  4435. }
  4436. return false;
  4437. }
  4438. void CFuncNewTeamWall::WallTouch( CBaseEntity *pOther )
  4439. {
  4440. //if ( !m_bShowWarning )
  4441. // return;
  4442. if ( pOther && pOther->IsPlayer() )
  4443. {
  4444. CDODPlayer *pPlayer = ToDODPlayer( pOther );
  4445. if ( pPlayer->GetTeamNumber() == GetTeamNumber() )
  4446. {
  4447. // show a "go away" icon
  4448. if ( m_flNextHintTime < gpGlobals->curtime )
  4449. {
  4450. pPlayer->HintMessage( "#dod_wrong_way" );
  4451. // global timer, but not critical to keep timer per player.
  4452. m_flNextHintTime = gpGlobals->curtime + 1.0;
  4453. }
  4454. }
  4455. }
  4456. }
  4457. #else
  4458. //-----------------------------------------------------------------------------
  4459. // Purpose:
  4460. //-----------------------------------------------------------------------------
  4461. class C_FuncNewTeamWall : public C_BaseEntity
  4462. {
  4463. DECLARE_CLASS( C_FuncNewTeamWall, C_BaseEntity );
  4464. public:
  4465. DECLARE_CLIENTCLASS();
  4466. virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
  4467. virtual int DrawModel( int flags );
  4468. };
  4469. IMPLEMENT_CLIENTCLASS_DT( C_FuncNewTeamWall, DT_FuncNewTeamWall, CFuncNewTeamWall )
  4470. END_RECV_TABLE()
  4471. //-----------------------------------------------------------------------------
  4472. // Purpose:
  4473. //-----------------------------------------------------------------------------
  4474. bool C_FuncNewTeamWall::ShouldCollide( int collisionGroup, int contentsMask ) const
  4475. {
  4476. if ( GetTeamNumber() == TEAM_UNASSIGNED )
  4477. return false;
  4478. if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
  4479. {
  4480. switch( GetTeamNumber() )
  4481. {
  4482. case TEAM_ALLIES:
  4483. if ( !(contentsMask & CONTENTS_TEAM1) )
  4484. return false;
  4485. break;
  4486. case TEAM_AXIS:
  4487. if ( !(contentsMask & CONTENTS_TEAM2) )
  4488. return false;
  4489. break;
  4490. }
  4491. return true;
  4492. }
  4493. return false;
  4494. }
  4495. int C_FuncNewTeamWall::DrawModel( int flags )
  4496. {
  4497. return 1;
  4498. }
  4499. #endif