diff options
Diffstat (limited to 'game/shared/dod/dod_gamerules.cpp')
| -rw-r--r-- | game/shared/dod/dod_gamerules.cpp | 5595 |
1 files changed, 5595 insertions, 0 deletions
diff --git a/game/shared/dod/dod_gamerules.cpp b/game/shared/dod/dod_gamerules.cpp new file mode 100644 index 0000000..9a9e48d --- /dev/null +++ b/game/shared/dod/dod_gamerules.cpp @@ -0,0 +1,5595 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The TF Game rules +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "dod_gamerules.h" +#include "ammodef.h" +#include "KeyValues.h" +#include "weapon_dodbase.h" +#include "filesystem.h" // for WriteStatsFile + + +#ifdef CLIENT_DLL + + #include "precache_register.h" + #include "c_dod_player.h" + +#else + + #include "coordsize.h" + #include "dod_player.h" + #include "voice_gamemgr.h" + #include "team.h" + #include "dod_player.h" + #include "dod_bot_temp.h" + #include "game.h" + #include "dod_shareddefs.h" + #include "player_resource.h" + #include "mapentities.h" + #include "dod_gameinterface.h" + #include "dod_objective_resource.h" + #include "dod_cvars.h" + #include "dod_team.h" + #include "dod_playerclass_info_parse.h" + #include "dod_control_point_master.h" + #include "dod_bombtarget.h" + //#include "teamplayroundbased_gamerules.h" + #include "weapon_dodbipodgun.h" + +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef CLIENT_DLL + +BEGIN_DATADESC(CSpawnPoint) + + // Keyfields + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + +END_DATADESC(); + +LINK_ENTITY_TO_CLASS(info_player_allies, CSpawnPoint); +LINK_ENTITY_TO_CLASS(info_player_axis, CSpawnPoint); + +#endif + +REGISTER_GAMERULES_CLASS( CDODGameRules ); + +#define MAX_RESPAWN_WAVES_TO_TRANSMIT 5 + +#ifdef CLIENT_DLL +void RecvProxy_RoundState( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + CDODGameRules *pGamerules = ( CDODGameRules *)pStruct; + + int iRoundState = pData->m_Value.m_Int; + + pGamerules->SetRoundState( iRoundState ); +} +#endif + +BEGIN_NETWORK_TABLE_NOBASE( CDODGameRules, DT_DODGameRules ) + #ifdef CLIENT_DLL + + RecvPropInt( RECVINFO( m_iRoundState ), 0, RecvProxy_RoundState ), + RecvPropBool( RECVINFO( m_bInWarmup ) ), + RecvPropBool( RECVINFO( m_bAwaitingReadyRestart ) ), + RecvPropTime( RECVINFO( m_flMapResetTime ) ), + RecvPropTime( RECVINFO( m_flRestartRoundTime ) ), + RecvPropBool( RECVINFO( m_bAlliesAreBombing ) ), + RecvPropBool( RECVINFO( m_bAxisAreBombing ) ), + + RecvPropArray3( RECVINFO_ARRAY(m_AlliesRespawnQueue), RecvPropTime( RECVINFO(m_AlliesRespawnQueue[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_AxisRespawnQueue), RecvPropTime( RECVINFO(m_AxisRespawnQueue[0]) ) ), + RecvPropInt( RECVINFO( m_iAlliesRespawnHead ) ), + RecvPropInt( RECVINFO( m_iAlliesRespawnTail ) ), + RecvPropInt( RECVINFO( m_iAxisRespawnHead ) ), + RecvPropInt( RECVINFO( m_iAxisRespawnTail ) ), + + #else + + SendPropInt( SENDINFO( m_iRoundState ), 5 ), + SendPropBool( SENDINFO( m_bInWarmup ) ), + SendPropBool( SENDINFO( m_bAwaitingReadyRestart ) ), + SendPropTime( SENDINFO( m_flMapResetTime ) ), + SendPropTime( SENDINFO( m_flRestartRoundTime ) ), + SendPropBool( SENDINFO( m_bAlliesAreBombing ) ), + SendPropBool( SENDINFO( m_bAxisAreBombing ) ), + + SendPropArray3( SENDINFO_ARRAY3(m_AlliesRespawnQueue), SendPropTime( SENDINFO_ARRAY(m_AlliesRespawnQueue) ) ), + SendPropArray3( SENDINFO_ARRAY3(m_AxisRespawnQueue), SendPropTime( SENDINFO_ARRAY(m_AxisRespawnQueue) ) ), + SendPropInt( SENDINFO( m_iAlliesRespawnHead ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iAlliesRespawnTail ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iAxisRespawnHead ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iAxisRespawnTail ), Q_log2( DOD_RESPAWN_QUEUE_SIZE ) + 1, SPROP_UNSIGNED ), + + #endif +END_NETWORK_TABLE() + +#ifndef CLIENT_DLL +ConVar dod_flagrespawnbonus( "dod_flagrespawnbonus", "1.0", FCVAR_GAMEDLL | FCVAR_CHEAT, "How many seconds per advantage flag to decrease the respawn time" ); + +ConVar mp_warmup_time( "mp_warmup_time", "0", FCVAR_GAMEDLL, "Warmup time length in seconds" ); +ConVar mp_restartwarmup( "mp_restartwarmup", "0", FCVAR_GAMEDLL, "Set to 1 to start or restart the warmup period." ); +ConVar mp_cancelwarmup( "mp_cancelwarmup", "0", FCVAR_GAMEDLL, "Set to 1 to end the warmup period." ); +#endif + +ConVar dod_enableroundwaittime( "dod_enableroundwaittime", "1", FCVAR_REPLICATED, "Enable timers to wait between rounds." ); +ConVar mp_allowrandomclass( "mp_allowrandomclass", "1", FCVAR_REPLICATED, "Allow players to select random class" ); + +ConVar dod_bonusroundtime( "dod_bonusroundtime", "15", FCVAR_REPLICATED, "Time after round win until round restarts", true, 5, true, 15 ); + +LINK_ENTITY_TO_CLASS( dod_gamerules, CDODGameRulesProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( DODGameRulesProxy, DT_DODGameRulesProxy ) + + +#ifdef CLIENT_DLL + void RecvProxy_DODGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) + { + CDODGameRules *pRules = DODGameRules(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CDODGameRulesProxy, DT_DODGameRulesProxy ) + RecvPropDataTable( "dod_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_DODGameRules ), RecvProxy_DODGameRules ) + END_RECV_TABLE() +#else + void* SendProxy_DODGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) + { + CDODGameRules *pRules = DODGameRules(); + Assert( pRules ); + pRecipients->SetAllRecipients(); + return pRules; + } + + BEGIN_SEND_TABLE( CDODGameRulesProxy, DT_DODGameRulesProxy ) + SendPropDataTable( "dod_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_DODGameRules ), SendProxy_DODGameRules ) + END_SEND_TABLE() +#endif + +static CDODViewVectors g_DODViewVectors( + + Vector( 0, 0, 58 ), //VEC_VIEW (m_vView) + + Vector(-16, -16, 0 ), //VEC_HULL_MIN (m_vHullMin) + Vector( 16, 16, 72 ), //VEC_HULL_MAX (m_vHullMax) + + Vector(-16, -16, 0 ), //VEC_DUCK_HULL_MIN (m_vDuckHullMin) + Vector( 16, 16, 45 ), //VEC_DUCK_HULL_MAX (m_vDuckHullMax) + Vector( 0, 0, 34 ), //VEC_DUCK_VIEW (m_vDuckView) + + Vector(-10, -10, -10 ), //VEC_OBS_HULL_MIN (m_vObsHullMin) + Vector( 10, 10, 10 ), //VEC_OBS_HULL_MAX (m_vObsHullMax) + + Vector( 0, 0, 14 ), //VEC_DEAD_VIEWHEIGHT (m_vDeadViewHeight) + + Vector(-16, -16, 0 ), //VEC_PRONE_HULL_MIN (m_vProneHullMin) + Vector( 16, 16, 24 ) //VEC_PRONE_HULL_MAX (m_vProneHullMax) +); + + +#ifdef CLIENT_DLL + + +#else + + void ParseEntKVBlock( CBaseEntity *pNode, KeyValues *pkvNode ) + { + KeyValues *pkvNodeData = pkvNode->GetFirstSubKey(); + while ( pkvNodeData ) + { + // Handle the connections block + if ( !Q_strcmp(pkvNodeData->GetName(), "connections") ) + { + ParseEntKVBlock( pNode, pkvNodeData ); + } + else + { + pNode->KeyValue( pkvNodeData->GetName(), pkvNodeData->GetString() ); + } + + pkvNodeData = pkvNodeData->GetNextKey(); + } + } + + CUtlVector<EHANDLE> m_hSpawnedEntities; + + // for now only allow blocker walls to load this way + bool CanLoadEntityFromEntText( const char *clsName ) + { + if ( !Q_strcmp( clsName, "func_team_wall" ) ) + { + return true; + } + + return false; + } + + void Load_EntText( void ) + { + bool oldLock = engine->LockNetworkStringTables( false ); + + // remove all ents in m_SpawnedEntities + for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ ) + { + UTIL_Remove( m_hSpawnedEntities[i] ); + } + + // delete the items from our list + m_hSpawnedEntities.RemoveAll(); + + // Find the commentary file + char szFullName[512]; + Q_snprintf(szFullName,sizeof(szFullName), "maps/%s.ent", STRING( gpGlobals->mapname )); + KeyValues *pkvFile = new KeyValues( "EntText" ); + if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) + { + DevMsg( "Load_EntText: Loading entity data from %s. \n", szFullName ); + + // Load each commentary block, and spawn the entities + KeyValues *pkvNode = pkvFile->GetFirstSubKey(); + while ( pkvNode ) + { + // Get node name + const char *pNodeName = pkvNode->GetName(); + KeyValues *pClassname = pkvNode->FindKey( "classname" ); + if ( pClassname ) + { + // Use the classname instead + pNodeName = pClassname->GetString(); + } + + if ( CanLoadEntityFromEntText( pNodeName ) ) + { + // Spawn the entity + CBaseEntity *pNode = CreateEntityByName( pNodeName ); + if ( pNode ) + { + ParseEntKVBlock( pNode, pkvNode ); + DispatchSpawn( pNode ); + + EHANDLE hHandle; + hHandle = pNode; + m_hSpawnedEntities.AddToTail( hHandle ); + } + else + { + Warning("Load_EntText: Failed to spawn entity, type: '%s'\n", pNodeName ); + } + } + + // Move to next entity + pkvNode = pkvNode->GetNextKey(); + } + + // Then activate all the entities + for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ ) + { + m_hSpawnedEntities[i]->Activate(); + } + } + else + { + DevMsg( "Load_EntText: Could not find entity data file '%s'. \n", szFullName ); + } + + engine->LockNetworkStringTables( oldLock ); + } + + // --------------------------------------------------------------------------------------------------- // + // Voice helper + // --------------------------------------------------------------------------------------------------- // + + class CVoiceGameMgrHelper : public IVoiceGameMgrHelper + { + public: + virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity ) + { + // Dead players can only be heard by other dead team mates + if ( pTalker->IsAlive() == false ) + { + if ( pListener->IsAlive() == false ) + return ( pListener->InSameTeam( pTalker ) ); + + return false; + } + + return ( pListener->InSameTeam( pTalker ) ); + } + }; + CVoiceGameMgrHelper g_VoiceGameMgrHelper; + IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; + + + + // --------------------------------------------------------------------------------------------------- // + // Globals. + // --------------------------------------------------------------------------------------------------- // + + // NOTE: the indices here must match TEAM_TERRORIST, TEAM_ALLIES, TEAM_AXIS, etc. + char *sTeamNames[] = + { + "Unassigned", + "Spectator", + "Allies", + "Axis" + }; + + static const char *s_PreserveEnts[] = + { + "player", + "viewmodel", + "worldspawn", + "soundent", + "ai_network", + "ai_hint", + "dod_gamerules", + "dod_team_manager", + "dod_player_manager", + "dod_objective_resource", + "env_soundscape", + "env_soundscape_proxy", + "env_soundscape_triggerable", + "env_sprite", + "env_sun", + "env_wind", + "env_fog_controller", + "func_brush", + "func_wall", + "func_illusionary", + "info_node", + "info_target", + "info_node_hint", + "info_player_allies", + "info_player_axis", + "point_viewcontrol", + "shadow_control", + "sky_camera", + "scene_manager", + "trigger_soundscape", + "info_dod_detect", + "dod_team_allies", + "dod_team_axis", + "point_commentary_node", + "dod_round_timer", + "func_precipitation", + "func_team_wall", + "", // END Marker + }; + + // --------------------------------------------------------------------------------------------------- // + // Global helper functions. + // --------------------------------------------------------------------------------------------------- // + + // World.cpp calls this but we don't use it in DoD. + void InitBodyQue() + { + } + + + // Handler for the "bot" command. + CON_COMMAND_F( bot, "Add a bot.", FCVAR_CHEAT ) + { + //CDODPlayer *pPlayer = CDODPlayer::Instance( UTIL_GetCommandClientIndex() ); + + // The bot command uses switches like command-line switches. + // -count <count> tells how many bots to spawn. + // -team <index> selects the bot's team. Default is -1 which chooses randomly. + // Note: if you do -team !, then it + // -class <index> selects the bot's class. Default is -1 which chooses randomly. + // -frozen prevents the bots from running around when they spawn in. + + // Look at -count. + int count = args.FindArgInt( "-count", 1 ); + count = clamp( count, 1, 16 ); + + int iTeam = TEAM_ALLIES; + const char *pVal = args.FindArg( "-team" ); + if ( pVal ) + { + iTeam = atoi( pVal ); + iTeam = clamp( iTeam, 0, (GetNumberOfTeams()-1) ); + } + + int iClass = 0; + pVal = args.FindArg( "-class" ); + if ( pVal ) + { + iClass = atoi( pVal ); + iClass = clamp( iClass, 0, 10 ); + } + + // Look at -frozen. + bool bFrozen = !!args.FindArg( "-frozen" ); + + // Ok, spawn all the bots. + while ( --count >= 0 ) + { + BotPutInServer( bFrozen, iTeam, iClass ); + } + } + + + void RestartRound_f() + { + DODGameRules()->State_Transition( STATE_RESTART ); + } + ConCommand cc_Restart( "restartround", RestartRound_f, "Restart the round", FCVAR_CHEAT ); + + void CDODGameRules::CopyGamePlayLogic( const CDODGamePlayRules otherGamePlay ) + { + m_GamePlayRules.CopyFrom( otherGamePlay ); + } + + // --------------------------------------------------------------------------------------------------- // + // CDODGameRules implementation. + // --------------------------------------------------------------------------------------------------- // + + CDODGameRules::CDODGameRules() + { + InitTeams(); + + ResetMapTime(); + + m_GamePlayRules.Reset(); + + ResetScores(); + + m_bInWarmup = false; + m_bAwaitingReadyRestart = false; + m_flRestartRoundTime = -1; + + m_iAlliesRespawnHead = 0; + m_iAlliesRespawnTail = 0; + m_iAxisRespawnHead = 0; + m_iAxisRespawnTail = 0; + m_iNumAlliesRespawnWaves = 0; + m_iNumAxisRespawnWaves = 0; + + for ( int i=0; i <DOD_RESPAWN_QUEUE_SIZE; i++ ) + { + m_AlliesRespawnQueue.Set( i, 0 ); + m_AxisRespawnQueue.Set( i, 0 ); + } + + m_bLevelInitialized = false; + m_iSpawnPointCount_Allies = 0; + m_iSpawnPointCount_Axis = 0; + + Q_memset( m_vecPlayerPositions,0, sizeof(m_vecPlayerPositions) ); + + // Lets execute a map specific cfg file + // Matt - execute this after server.cfg! + char szCommand[256] = { 0 }; + // Map names cannot contain quotes or control characters so this is safe but silly that we have to do it. + Q_snprintf( szCommand, sizeof(szCommand), "exec \"%s.cfg\"\n", STRING(gpGlobals->mapname) ); + engine->ServerCommand( szCommand ); + + m_pCurStateInfo = NULL; + State_Transition( STATE_PREGAME ); + + // stats + memset( m_iStatsKillsPerClass_Allies, 0, sizeof(m_iStatsKillsPerClass_Allies) ); + memset( m_iStatsKillsPerClass_Axis, 0, sizeof(m_iStatsKillsPerClass_Axis) ); + + memset( m_iStatsSpawnsPerClass_Allies, 0, sizeof(m_iStatsSpawnsPerClass_Allies) ); + memset( m_iStatsSpawnsPerClass_Axis, 0, sizeof(m_iStatsSpawnsPerClass_Axis) ); + + memset( m_iStatsCapsPerClass_Allies, 0, sizeof(m_iStatsCapsPerClass_Allies) ); + memset( m_iStatsCapsPerClass_Axis, 0, sizeof(m_iStatsCapsPerClass_Axis) ); + + memset( m_iStatsDefensesPerClass_Allies, 0, sizeof(m_iStatsDefensesPerClass_Allies) ); + memset( m_iStatsDefensesPerClass_Axis, 0, sizeof(m_iStatsDefensesPerClass_Axis) ); + + memset( &m_iWeaponShotsFired, 0, sizeof(m_iWeaponShotsFired) ); + memset( &m_iWeaponShotsHit, 0, sizeof(m_iWeaponShotsHit) ); + memset( &m_iWeaponDistanceBuckets, 0, sizeof(m_iWeaponDistanceBuckets) ); + + memset( &m_flSecondsPlayedPerClass_Allies, 0, sizeof(m_flSecondsPlayedPerClass_Allies) ); + memset( &m_flSecondsPlayedPerClass_Axis, 0, sizeof(m_flSecondsPlayedPerClass_Axis) ); + + m_bUsingTimer = false; + m_pRoundTimer = NULL; // created on first round spawn that requires a timer + + m_bAlliesAreBombing = false; + m_bAxisAreBombing = false; + + m_flNextFailSafeWaveCheckTime = 0; + + // Init the holiday + int day = 0, month = 0, year = 0; + +#ifdef WIN32 + GetCurrentDate( &day, &month, &year ); +#elif POSIX + time_t now = time(NULL); + struct tm *tm = localtime( &now ); + + day = tm->tm_mday + 1; + month = tm->tm_mon; + year = tm->tm_year + 1900; +#endif + + if ( ( month == 12 && day >= 1 ) || ( month == 1 && day <= 2 ) ) + { + m_bWinterHolidayActive = true; + } + else + { + m_bWinterHolidayActive = false; + } + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + CDODGameRules::~CDODGameRules() + { + // Note, don't delete each team since they are in the gEntList and will + // automatically be deleted from there, instead. + g_Teams.Purge(); + } + + void CDODGameRules::LevelShutdown( void ) + { + UploadLevelStats(); + + BaseClass::LevelShutdown(); + } + + #define MY_USHRT_MAX 0xffff + #define MY_UCHAR_MAX 0xff + + void CDODGameRules::UploadLevelStats( void ) + { + if ( Q_strlen( STRING( gpGlobals->mapname ) ) > 0 ) + { + int i,j; + CDODTeam *pAllies = GetGlobalDODTeam( TEAM_ALLIES ); + CDODTeam *pAxis = GetGlobalDODTeam( TEAM_AXIS ); + + dod_gamestats_t stats; + memset( &stats, 0, sizeof(stats) ); + + // Header + stats.header.iVersion = DOD_STATS_BLOB_VERSION; + Q_strncpy( stats.header.szGameName, "dod", sizeof(stats.header.szGameName) ); + Q_strncpy( stats.header.szMapName, STRING( gpGlobals->mapname ), sizeof( stats.header.szMapName ) ); + + ConVar *hostip = cvar->FindVar( "hostip" ); + if ( hostip ) + { + int ip = hostip->GetInt(); + stats.header.ipAddr[0] = ip >> 24; + stats.header.ipAddr[1] = ( ip >> 16 ) & 0xff; + stats.header.ipAddr[2] = ( ip >> 8 ) & 0xff; + stats.header.ipAddr[3] = ( ip ) & 0xff; + } + + ConVar *hostport = cvar->FindVar( "hostip" ); + if ( hostport ) + { + stats.header.port = hostport->GetInt(); + } + + stats.header.serverid = 0; + + stats.iMinutesPlayed = clamp( (short)( gpGlobals->curtime / 60 ), 0, MY_USHRT_MAX ); + + // Team Scores + stats.iNumAlliesWins = clamp( pAllies->GetRoundsWon(), 0, MY_UCHAR_MAX ); + stats.iNumAxisWins = clamp( pAxis->GetRoundsWon(), 0, MY_UCHAR_MAX ); + + stats.iAlliesTickPoints = clamp( pAllies->GetScore(), 0, MY_USHRT_MAX ); + stats.iAxisTickPoints = clamp( pAxis->GetScore(), 0, MY_USHRT_MAX ); + + // Player Data + for ( i=1;i<=MAX_PLAYERS;i++ ) + { + CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer ) + { + // Sum up the time played for players that are still connected. + // players who disconnected had their connect time added in ClientDisconnected + + // Tally the latest time for this player + pPlayer->TallyLatestTimePlayedPerClass( pPlayer->GetTeamNumber(), pPlayer->m_Shared.DesiredPlayerClass() ); + + for( j=0;j<7;j++ ) + { + m_flSecondsPlayedPerClass_Allies[j] += pPlayer->m_flTimePlayedPerClass_Allies[j]; + m_flSecondsPlayedPerClass_Axis[j] += pPlayer->m_flTimePlayedPerClass_Axis[j]; + } + } + } + + // convert to minutes + for( j=0;j<7;j++ ) + { + stats.iMinutesPlayedPerClass_Allies[j] = clamp( (short)( m_flSecondsPlayedPerClass_Allies[j] / 60 ), 0, MY_USHRT_MAX ); + stats.iMinutesPlayedPerClass_Axis[j] = clamp( (short)( m_flSecondsPlayedPerClass_Axis[j] / 60 ), 0, MY_USHRT_MAX ); + } + + for ( i=0;i<6;i++ ) + { + stats.iKillsPerClass_Allies[i] = clamp( (short)m_iStatsKillsPerClass_Allies[i], 0, MY_USHRT_MAX ); + stats.iKillsPerClass_Axis[i] = clamp( (short)m_iStatsKillsPerClass_Axis[i], 0, MY_USHRT_MAX ); + + stats.iSpawnsPerClass_Allies[i] = clamp( (short)m_iStatsSpawnsPerClass_Allies[i], 0, MY_USHRT_MAX ); + stats.iSpawnsPerClass_Axis[i] = clamp( (short)m_iStatsSpawnsPerClass_Axis[i], 0, MY_USHRT_MAX ); + + stats.iCapsPerClass_Allies[i] = clamp( (short)m_iStatsCapsPerClass_Allies[i], 0, MY_USHRT_MAX ); + stats.iCapsPerClass_Axis[i] = clamp( (short)m_iStatsCapsPerClass_Axis[i], 0, MY_USHRT_MAX ); + + stats.iDefensesPerClass_Allies[i] = clamp( m_iStatsDefensesPerClass_Allies[i], 0, MY_UCHAR_MAX ); + stats.iDefensesPerClass_Axis[i] = clamp( m_iStatsDefensesPerClass_Axis[i], 0, MY_UCHAR_MAX ); + } + + // Server Settings + stats.iClassLimits_Allies[0] = clamp( mp_limitAlliesRifleman.GetInt(), -1, 254 ); + stats.iClassLimits_Allies[1] = clamp( mp_limitAlliesAssault.GetInt(), -1, 254 ); + stats.iClassLimits_Allies[2] = clamp( mp_limitAlliesSupport.GetInt(), -1, 254 ); + stats.iClassLimits_Allies[3] = clamp( mp_limitAlliesSniper.GetInt(), -1, 254 ); + stats.iClassLimits_Allies[4] = clamp( mp_limitAlliesMachinegun.GetInt(), -1, 254 ); + stats.iClassLimits_Allies[5] = clamp( mp_limitAlliesRocket.GetInt(), -1, 254 ); + + stats.iClassLimits_Axis[0] = clamp( mp_limitAxisRifleman.GetInt(), -1, 254 ); + stats.iClassLimits_Axis[1] = clamp( mp_limitAxisAssault.GetInt(), -1, 254 ); + stats.iClassLimits_Axis[2] = clamp( mp_limitAxisSupport.GetInt(), -1, 254 ); + stats.iClassLimits_Axis[3] = clamp( mp_limitAxisSniper.GetInt(), -1, 254 ); + stats.iClassLimits_Axis[4] = clamp( mp_limitAxisMachinegun.GetInt(), -1, 254 ); + stats.iClassLimits_Axis[5] = clamp( mp_limitAxisRocket.GetInt(), -1, 254 ); + + // Weapon Data + + // Send hit/shots/distance info for the following guns / modes + for ( i=0;i<DOD_NUM_DISTANCE_STAT_WEAPONS;i++ ) + { + int weaponId = iDistanceStatWeapons[i]; + + stats.weaponStatsDistance[i].iNumHits = clamp( m_iWeaponShotsHit[weaponId], 0, MY_USHRT_MAX ); + stats.weaponStatsDistance[i].iNumAttacks = clamp( m_iWeaponShotsFired[weaponId], 0, MY_USHRT_MAX ); + for ( int j=0;j<DOD_NUM_WEAPON_DISTANCE_BUCKETS;j++ ) + { + stats.weaponStatsDistance[i].iDistanceBuckets[j] = clamp( m_iWeaponDistanceBuckets[weaponId][j], 0, MY_USHRT_MAX ); + } + } + + // Send hit/shots info for the following guns / modes + for ( i=0;i<DOD_NUM_NODIST_STAT_WEAPONS;i++ ) + { + int weaponId = iNoDistStatWeapons[i]; + stats.weaponStats[i].iNumHits = clamp( m_iWeaponShotsHit[weaponId], 0, MY_USHRT_MAX ); + stats.weaponStats[i].iNumAttacks = clamp( m_iWeaponShotsFired[weaponId], 0, MY_USHRT_MAX ); + } + + const void *pvBlobData = ( const void * )( &stats ); + unsigned int uBlobSize = sizeof( stats ); + + if ( gamestatsuploader ) + { + gamestatsuploader->UploadGameStats( + STRING( gpGlobals->mapname ), + DOD_STATS_BLOB_VERSION, + uBlobSize, + pvBlobData ); + } + } + } + + void CDODGameRules::Stats_PlayerKill( int team, int cls ) + { + Assert( cls >= 0 && cls <= 5 ); + + if ( cls >= 0 && cls <= 5 ) + { + if ( team == TEAM_ALLIES ) + m_iStatsKillsPerClass_Allies[cls]++; + else if ( team == TEAM_AXIS ) + m_iStatsKillsPerClass_Axis[cls]++; + } + } + + void CDODGameRules::Stats_PlayerCap( int team, int cls ) + { + Assert( cls >= 0 && cls <= 5 ); + + if ( cls >= 0 && cls <= 5 ) + { + if ( team == TEAM_ALLIES ) + m_iStatsCapsPerClass_Allies[cls]++; + else if ( team == TEAM_AXIS ) + m_iStatsCapsPerClass_Axis[cls]++; + } + } + + void CDODGameRules::Stats_PlayerDefended( int team, int cls ) + { + Assert( cls >= 0 && cls <= 5 ); + + if ( cls >= 0 && cls <= 5 ) + { + if ( team == TEAM_ALLIES ) + m_iStatsDefensesPerClass_Allies[cls]++; + else if ( team == TEAM_AXIS ) + m_iStatsDefensesPerClass_Axis[cls]++; + } + } + + void CDODGameRules::Stats_WeaponFired( int weaponID ) + { + m_iWeaponShotsFired[weaponID]++; + } + + void CDODGameRules::Stats_WeaponHit( int weaponID, float flDist ) + { + m_iWeaponShotsHit[weaponID]++; + + int bucket = Stats_WeaponDistanceToBucket( weaponID, flDist ); + m_iWeaponDistanceBuckets[weaponID][bucket]++; + } + + int CDODGameRules::Stats_WeaponDistanceToBucket( int weaponID, float flDist ) + { + int bucket = 4; + int iDist = (int)flDist; + + for ( int i=0;i<DOD_NUM_WEAPON_DISTANCE_BUCKETS-1;i++ ) + { + if ( iDist < iWeaponBucketDistances[i] ) + { + bucket = i; + break; + } + } + + return bucket; + } + + + //----------------------------------------------------------------------------- + // Purpose: DoD Specific Client Commands + // Input : + // Output : + //----------------------------------------------------------------------------- + bool CDODGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) + { + CDODPlayer *pPlayer = ToDODPlayer( pEdict ); + const char *pcmd = args[0]; +#ifdef DEBUG + if ( FStrEq( pcmd, "teamwin" ) ) + { + if ( args.ArgC() < 2 ) + return true; + + SetWinningTeam( atoi( args[1] ) ); + + return true; + } + else +#endif + // Handle some player commands here as they relate more directly to gamerules state + if ( FStrEq( pcmd, "nextmap" ) ) + { + CDODPlayer *pDODPlayer = ToDODPlayer(pPlayer); + + if ( pDODPlayer->m_flNextTimeCheck < gpGlobals->curtime ) + { + char szNextMap[32]; + + if ( nextlevel.GetString() && *nextlevel.GetString() ) + { + Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) ); + } + else + { + GetNextLevelName( szNextMap, sizeof( szNextMap ) ); + } + + ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", szNextMap); + + pDODPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1; + } + + return true; + } + else if ( FStrEq( pcmd, "timeleft" ) ) + { + CDODPlayer *pDODPlayer = ToDODPlayer(pPlayer); + + if ( pDODPlayer->m_flNextTimeCheck < gpGlobals->curtime ) + { + if ( mp_timelimit.GetInt() > 0 ) + { + int iTimeLeft = GetTimeLeft(); + + char szMinutes[5]; + char szSeconds[3]; + + if ( iTimeLeft <= 0 ) + { + Q_snprintf( szMinutes, sizeof(szMinutes), "0" ); + Q_snprintf( szSeconds, sizeof(szSeconds), "00" ); + } + else + { + Q_snprintf( szMinutes, sizeof(szMinutes), "%d", iTimeLeft / 60 ); + Q_snprintf( szSeconds, sizeof(szSeconds), "%02d", iTimeLeft % 60 ); + } + + ClientPrint( pPlayer, HUD_PRINTTALK, "#game_time_left1", szMinutes, szSeconds ); + } + else + { + ClientPrint( pPlayer, HUD_PRINTTALK, "#game_time_left2" ); + } + + CDODPlayer *pDODPlayer = ToDODPlayer(pPlayer); + pDODPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1; + } + return true; + } + else if ( pPlayer->ClientCommand( args ) ) + { + return true; + } + else if ( BaseClass::ClientCommand( pEdict, args ) ) + { + return true; + } + + return false; + } + + void CDODGameRules::CheckChatForReadySignal( CDODPlayer *pPlayer, const char *chatmsg ) + { + if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_clan_ready_signal.GetString() ) ) + { + if( !m_bHeardAlliesReady && pPlayer->GetTeamNumber() == TEAM_ALLIES ) + { + m_bHeardAlliesReady = true; + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_allies_ready" ); + if ( event ) + gameeventmanager->FireEvent( event ); + } + else if( !m_bHeardAxisReady && pPlayer->GetTeamNumber() == TEAM_AXIS ) + { + m_bHeardAxisReady = true; + IGameEvent *event = gameeventmanager->CreateEvent( "dod_axis_ready" ); + if ( event ) + gameeventmanager->FireEvent( event ); + } + } + } + + int CDODGameRules::SelectDefaultTeam() + { + int team = TEAM_UNASSIGNED; + + CDODTeam *pAllies = GetGlobalDODTeam(TEAM_ALLIES); + CDODTeam *pAxis = GetGlobalDODTeam(TEAM_AXIS); + + int iNumAllies = pAllies->GetNumPlayers(); + int iNumAxis = pAxis->GetNumPlayers(); + + int iAlliesRoundsWon = pAllies->GetRoundsWon(); + int iAxisRoundsWon = pAxis->GetRoundsWon(); + + int iAlliesPoints = pAllies->GetScore(); + int iAxisPoints = pAxis->GetScore(); + + // Choose the team that's lacking players + if ( iNumAllies < iNumAxis ) + { + team = TEAM_ALLIES; + } + else if ( iNumAllies > iNumAxis ) + { + team = TEAM_AXIS; + } + // Choose the team that's losing + else if ( iAlliesRoundsWon < iAxisRoundsWon ) + { + team = TEAM_ALLIES; + } + else if ( iAlliesRoundsWon > iAxisRoundsWon ) + { + team = TEAM_AXIS; + } + // choose the team with fewer points + else if ( iAlliesPoints < iAxisPoints ) + { + team = TEAM_ALLIES; + } + else if ( iAlliesPoints > iAxisPoints ) + { + team = TEAM_AXIS; + } + else + { + // Teams and scores are equal, pick a random team + team = ( random->RandomInt(0,1) == 0 ) ? TEAM_ALLIES : TEAM_AXIS; + } + + if ( TeamFull( team ) ) + { + // Pick the opposite team + if ( team == TEAM_ALLIES ) + { + team = TEAM_AXIS; + } + else + { + team = TEAM_ALLIES; + } + + // No choices left + if ( TeamFull( team ) ) + return TEAM_UNASSIGNED; + } + + return team; + } + + bool CDODGameRules::TeamFull( int team_id ) + { + switch ( team_id ) + { + case TEAM_ALLIES: + { + int iNumAllies = GetGlobalDODTeam(TEAM_ALLIES)->GetNumPlayers(); + return iNumAllies >= m_iSpawnPointCount_Allies; + } + case TEAM_AXIS: + { + int iNumAxis = GetGlobalDODTeam(TEAM_AXIS)->GetNumPlayers(); + return iNumAxis >= m_iSpawnPointCount_Axis; + } + } + + return false; + } + + + //----------------------------------------------------------------------------- + // Purpose: Player has just spawned. Equip them. + //----------------------------------------------------------------------------- + + // return a multiplier that should adjust the damage done by a blast at position vecSrc to something at the position + // vecEnd. This will take into account the density of an entity that blocks the line of sight from one position to + // the other. + // + // this algorithm was taken from the HL2 version of RadiusDamage. + float CDODGameRules::GetExplosionDamageAdjustment(Vector & vecSrc, Vector & vecEnd, CBaseEntity *pTarget, CBaseEntity *pEntityToIgnore) + { + float retval = 0.0; + trace_t tr; + + UTIL_TraceLine(vecSrc, vecEnd, MASK_SHOT, pEntityToIgnore, COLLISION_GROUP_NONE, &tr); + + Assert( pTarget ); + + // its a hit if we made it to the dest, or if we hit another part of the target on the way + if (tr.fraction == 1.0 || tr.m_pEnt == pTarget ) + { + retval = 1.0; + } + else if (!(tr.DidHitWorld()) && (tr.m_pEnt != NULL) && (tr.m_pEnt->GetOwnerEntity() != pEntityToIgnore)) + { + // if we didn't hit world geometry perhaps there's still damage to be done here. + + CBaseEntity *blockingEntity = tr.m_pEnt; + + // check to see if this part of the player is visible if entities are ignored. + UTIL_TraceLine(vecSrc, vecEnd, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &tr); + + if (tr.fraction == 1.0) + { + if ((blockingEntity != NULL) && (blockingEntity->VPhysicsGetObject() != NULL)) + { + int nMaterialIndex = blockingEntity->VPhysicsGetObject()->GetMaterialIndex(); + + float flDensity; + float flThickness; + float flFriction; + float flElasticity; + + physprops->GetPhysicsProperties( nMaterialIndex, &flDensity, + &flThickness, &flFriction, &flElasticity ); + + const float ONE_OVER_DENSITY_ABSORB_ALL_DAMAGE = ( 1.0 / 3000.0 ); + float scale = flDensity * ONE_OVER_DENSITY_ABSORB_ALL_DAMAGE; + + if ((scale >= 0.0) && (scale < 1.0)) + { + retval = 1.0 - scale; + } + else if (scale < 0.0) + { + // should never happen, but just in case. + retval = 1.0; + } + } + else + { + 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. + } + } + } + + return retval; + } + + // returns the percentage of the player that is visible from the given point in the world. + // return value is between 0 and 1. + float CDODGameRules::GetAmountOfEntityVisible(Vector & vecSrc, CBaseEntity *entity, CBaseEntity *pIgnoreEntity ) + { + float retval = 0.0; + + Vector vecHullSizeNormal = VEC_HULL_MAX - VEC_HULL_MIN; + + const float damagePercentageChest = 0.40; + const float damagePercentageHead = 0.30; + const float damagePercentageFoot = 0.10; // x 2 + const float damagePercentageHand = 0.05; // x 2 + + if (!(entity->IsPlayer())) + { + // the entity is not a player, so the damage is all or nothing. + Vector vecTarget; + vecTarget = entity->BodyTarget(vecSrc, false); + + return GetExplosionDamageAdjustment(vecSrc, vecTarget, entity, pIgnoreEntity); + } + + CDODPlayer *player = ToDODPlayer(entity); + + /* + new, sane method + */ + + static int iRHandIndex = 0; + static int iLHandIndex = 0; + static int iHeadIndex = 0; + static int iChestIndex = 0; + static int iRFootIndex = 0; + static int iLFootIndex = 0; + + static bool bInitializedBones = false; + + if ( !bInitializedBones ) + { + iRHandIndex = player->LookupBone( "ValveBiped.Bip01_R_Hand" ); + iLHandIndex = player->LookupBone( "ValveBiped.Bip01_L_Hand" ); + iHeadIndex = player->LookupBone( "ValveBiped.Bip01_Head1" ); + iChestIndex = player->LookupBone( "ValveBiped.Bip01_Spine2" ); + iRFootIndex = player->LookupBone( "ValveBiped.Bip01_R_Foot" ); + iLFootIndex = player->LookupBone( "ValveBiped.Bip01_L_Foot" ); + + Assert( iRHandIndex != -1 ); + Assert( iLHandIndex != -1 ); + Assert( iHeadIndex != -1 ); + Assert( iChestIndex != -1 ); + Assert( iRFootIndex != -1 ); + Assert( iLFootIndex != -1 ); + + bInitializedBones = true; + } + +#ifdef _DEBUG + // verify that these bone indeces don't change + int checkBoneIndex = player->LookupBone( "ValveBiped.Bip01_R_Hand" ); + Assert( checkBoneIndex == iRHandIndex ); +#endif + + + QAngle dummyAngle; + + Vector vecRHand; + player->GetBonePosition( iRHandIndex, vecRHand, dummyAngle ); + + Vector vecLHand; + player->GetBonePosition( iLHandIndex, vecLHand, dummyAngle ); + + Vector vecHead; + player->GetBonePosition( iHeadIndex, vecHead, dummyAngle ); + + Vector vecChest; + player->GetBonePosition( iChestIndex, vecChest, dummyAngle ); + + Vector vecRFoot; + player->GetBonePosition( iRFootIndex, vecRFoot, dummyAngle ); + + Vector vecLFoot; + player->GetBonePosition( iLFootIndex, vecLFoot, dummyAngle ); + + // right hand + float damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRHand, player, pIgnoreEntity ); + retval += (damagePercentageHand * damageAdjustment); + +/* + Msg( "right hand: %.1f\n", damageAdjustment ); + NDebugOverlay::Line( vecSrc, vecRHand, + (int)(damageAdjustment * 255.0), + (int)((1.0 - damageAdjustment) * 255.0), + 0, + true, + 10 );*/ + + + // left hand + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecLHand, player, pIgnoreEntity ); + retval += (damagePercentageHand * damageAdjustment); + +/* + Msg( "left hand: %.1f\n", damageAdjustment ); + NDebugOverlay::Line( vecSrc, vecLHand, + (int)(damageAdjustment * 255.0), + (int)((1.0 - damageAdjustment) * 255.0), + 0, + true, + 10 );*/ + + + // head + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecHead, player, pIgnoreEntity ); + retval += (damagePercentageHead * damageAdjustment); + +/* + Msg( "head: %.1f\n", damageAdjustment ); + NDebugOverlay::Line( vecSrc, vecHead, + (int)(damageAdjustment * 255.0), + (int)((1.0 - damageAdjustment) * 255.0), + 0, + true, + 10 );*/ + + + // chest + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecChest, player, pIgnoreEntity ); + retval += (damagePercentageChest * damageAdjustment); + +/* + Msg( "chest: %.1f\n", damageAdjustment ); + NDebugOverlay::Line( vecSrc, vecChest, + (int)(damageAdjustment * 255.0), + (int)((1.0 - damageAdjustment) * 255.0), + 0, + true, + 10 );*/ + + + // right foot + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRFoot, player, pIgnoreEntity ); + retval += (damagePercentageFoot * damageAdjustment); + +/* + Msg( "right foot: %.1f\n", damageAdjustment ); + NDebugOverlay::Line( vecSrc, vecRFoot, + (int)(damageAdjustment * 255.0), + (int)((1.0 - damageAdjustment) * 255.0), + 0, + true, + 10 );*/ + + + // left foot + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRFoot, player, pIgnoreEntity ); + retval += (damagePercentageFoot * damageAdjustment); + +/* + Msg( "left foot: %.1f\n", damageAdjustment ); + NDebugOverlay::Line( vecSrc, vecRFoot, + (int)(damageAdjustment * 255.0), + (int)((1.0 - damageAdjustment) * 255.0), + 0, + true, + 10 );*/ + + +// Msg( "total: %.1f\n", retval ); + + return retval; + } + + void CDODGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore ) + { + RadiusDamage( info, vecSrcIn, flRadius, iClassIgnore, pEntityIgnore, false ); + } + + ConVar r_visualizeExplosion( "r_visualizeExplosion", "0", FCVAR_CHEAT ); + + void CDODGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore, bool bIgnoreWorld /* = false */ ) + { + CBaseEntity *pEntity = NULL; + trace_t tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + Vector vecToTarget; + + Vector vecSrc = vecSrcIn; + + float flDamagePercentage; + + if ( flRadius ) + falloff = info.GetDamage() / flRadius; + else + falloff = 1.0; + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( r_visualizeExplosion.GetBool() ) + { + float flLethalRange = ( info.GetDamage() - 100 ) / falloff; + float flHalfDamageRange = ( info.GetDamage() - 50 ) / falloff; + float flZeroDamageRange = ( info.GetDamage() ) / falloff; + + // draw a red sphere representing the kill area + Vector dest = vecSrc; + dest.x += flLethalRange; + + NDebugOverlay::HorzArrow( vecSrc, dest, 10, 255, 0, 0, 255, true, 10.0 ); + + // yellow for 50 damage + dest = vecSrc; + dest.x += flHalfDamageRange; + + NDebugOverlay::HorzArrow( vecSrc, dest, 10, 255, 255, 0, 255, true, 10.0 ); + + // green for > 0 damage + dest = vecSrc; + dest.x += flZeroDamageRange; + + NDebugOverlay::HorzArrow( vecSrc, dest, 10, 0, 255, 0, 255, true, 10.0 ); + } + + // iterate on all entities in the vicinity. + for ( CEntitySphereQuery sphere( vecSrc, flRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + if ( pEntity->m_takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + continue; + + if ( pEntity == pEntityIgnore ) + continue; + + // radius damage can only be blocked by the world + vecSpot = pEntity->BodyTarget( vecSrc ); + + if ( bIgnoreWorld ) + { + flDamagePercentage = 1.0; + } + else + { + // get the percentage of the target entity that is visible from the + // explosion position. + flDamagePercentage = GetAmountOfEntityVisible(vecSrc, pEntity, info.GetInflictor() ); + } + + if (flDamagePercentage > 0.0) + { + // the explosion can 'see' this entity, so hurt them! + vecToTarget = ( vecSpot - vecSrc ); + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = vecToTarget.Length() * falloff; + flAdjustedDamage = info.GetDamage() - flAdjustedDamage; + + flAdjustedDamage *= flDamagePercentage; + + if ( flAdjustedDamage > 0 ) + { + CTakeDamageInfo adjustedInfo = info; + adjustedInfo.SetDamage( flAdjustedDamage ); + + Vector dir = vecToTarget; + VectorNormalize( dir ); + + // If we don't have a damage force, manufacture one + if ( adjustedInfo.GetDamagePosition() == vec3_origin || adjustedInfo.GetDamageForce() == vec3_origin ) + { + CalculateExplosiveDamageForce( &adjustedInfo, dir, vecSrc, 1.5 /* explosion scale! */ ); + } + else + { + // Assume the force passed in is the maximum force. Decay it based on falloff. + float flForce = adjustedInfo.GetDamageForce().Length() * falloff; + adjustedInfo.SetDamageForce( dir * flForce ); + adjustedInfo.SetDamagePosition( vecSrc ); + } + + pEntity->TakeDamage( adjustedInfo ); + + // Now hit all triggers along the way that respond to damage... + pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, vecSpot, dir ); + } + } + } + } + } + + void CDODGameRules::RadiusStun( const CTakeDamageInfo &info, const Vector &vecSrc, float flRadius ) + { + CBaseEntity *pEntity = NULL; + trace_t tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + Vector vecToTarget; + + if ( flRadius ) + falloff = info.GetDamage() / flRadius; + else + falloff = 1.0; + + // ok, now send updates to all clients + CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; + playerbits.ClearAll(); + + // see which players are actually in the PVS of the grenade + engine->Message_DetermineMulticastRecipients( false, vecSrc, playerbits ); + + // Iterate through all players that made it into playerbits, that are inside the radius + // and give them stun damage + for ( int i=0;i<MAX_PLAYERS;i++ ) + { + if ( playerbits.Get(i) == false ) + continue; + + pEntity = UTIL_EntityByIndex( i+1 ); + + if ( !pEntity || !pEntity->IsPlayer() ) + continue; + + if ( pEntity->m_takedamage != DAMAGE_NO ) + { + // radius damage can only be blocked by the world + vecSpot = pEntity->BodyTarget( vecSrc ); + + // the explosion can 'see' this entity, so hurt them! + vecToTarget = ( vecSpot - vecSrc ); + + float flDist = vecToTarget.Length(); + + // make sure they are inside the radius + if ( flDist > flRadius ) + continue; + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = flDist * falloff; + flAdjustedDamage = info.GetDamage() - flAdjustedDamage; + + if ( flAdjustedDamage > 0 ) + { + CTakeDamageInfo adjustedInfo = info; + adjustedInfo.SetDamage( flAdjustedDamage ); + + pEntity->TakeDamage( adjustedInfo ); + } + } + } + } + + void CDODGameRules::Think() + { + if ( g_fGameOver ) // someone else quit the game already + { + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->curtime ) + { + ChangeLevel(); // intermission is over + } + + return; + } + + State_Think(); + + if ( gpGlobals->curtime > m_flNextPeriodicThink ) + { + if ( CheckTimeLimit() ) + return; + + if ( CheckWinLimit() ) + return; + + CheckRestartRound(); + CheckWarmup(); + CheckPlayerPositions(); + + m_flNextPeriodicThink = gpGlobals->curtime + 1.0; + } + + CGameRules::Think(); + } + + void CDODGameRules::GoToIntermission( void ) + { + BaseClass::GoToIntermission(); + + // set all players to FL_FROZEN + for ( int i = 1; i <= MAX_PLAYERS; i++ ) + { + CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer ) + { + pPlayer->AddFlag( FL_FROZEN ); + + pPlayer->StatEvent_UploadStats(); + } + } + + // Print out map stats to a text file + //WriteStatsFile( "stats.xml" ); + + State_Enter( STATE_GAME_OVER ); + } + + void CDODGameRules::SetInWarmup( bool bWarmup ) + { + if( m_bInWarmup == bWarmup ) + return; + + m_bInWarmup = bWarmup; + + if( m_bInWarmup ) + { + m_flWarmupTimeEnds = gpGlobals->curtime + mp_warmup_time.GetFloat(); + DevMsg( "Warmup_Begin\n" ); + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_warmup_begins" ); + if ( event ) + gameeventmanager->FireEvent( event ); + } + else + { + m_flWarmupTimeEnds = -1; + DevMsg( "Warmup_Ends\n" ); + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_warmup_ends" ); + if ( event ) + gameeventmanager->FireEvent( event ); + } + } + + void CDODGameRules::CheckWarmup( void ) + { + if( mp_restartwarmup.GetBool() ) + { + if( m_bInWarmup ) + { + m_flWarmupTimeEnds = gpGlobals->curtime + mp_warmup_time.GetFloat(); + } + else + DODGameRules()->SetInWarmup( true ); + + mp_restartwarmup.SetValue( 0 ); + } + + if( mp_cancelwarmup.GetBool() ) + { + DODGameRules()->SetInWarmup( false ); + mp_cancelwarmup.SetValue( 0 ); + } + + if( m_bInWarmup ) + { + // only exit the warmup if the time is up, and we are not in a round + // restart countdown already, and we are not waiting for a ready restart + if( gpGlobals->curtime > m_flWarmupTimeEnds && m_flRestartRoundTime < 0 && !m_bAwaitingReadyRestart ) + { + // no need to end the warmup, the restart will end it automatically + //SetInWarmup( false ); + + m_flRestartRoundTime = gpGlobals->curtime; // reset asap + } + } + } + + void CDODGameRules::CheckRestartRound( void ) + { + if( mp_clan_readyrestart.GetBool() ) + { + m_bAwaitingReadyRestart = true; + m_bHeardAlliesReady = false; + m_bHeardAxisReady = false; + + const char *pszReadyString = mp_clan_ready_signal.GetString(); + + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#clan_ready_rules", pszReadyString ); + UTIL_ClientPrintAll( HUD_PRINTTALK, "#clan_ready_rules", pszReadyString ); + + // Don't let them put anything malicious in there + if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 ) + { + pszReadyString = "ready"; + } + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_ready_restart" ); + if ( event ) + gameeventmanager->FireEvent( event ); + + mp_clan_readyrestart.SetValue( 0 ); + + // cancel any restart round in progress + m_flRestartRoundTime = -1; + } + + // Restart the game if specified by the server + int iRestartDelay = mp_clan_restartround.GetInt(); + + if ( iRestartDelay > 0 ) + { + if ( iRestartDelay > 60 ) + iRestartDelay = 60; + + m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay; + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_restart_seconds" ); + if ( event ) + { + event->SetInt( "seconds", iRestartDelay ); + gameeventmanager->FireEvent( event ); + } + + mp_clan_restartround.SetValue( 0 ); + + // cancel any ready restart in progress + m_bAwaitingReadyRestart = false; + } + } + + bool CDODGameRules::CheckTimeLimit() + { + if ( IsGameUnderTimeLimit() ) + { + if( GetTimeLeft() <= 0 ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "dod_game_over" ); + if ( event ) + { + event->SetString( "reason", "Reached Time Limit" ); + gameeventmanager->FireEvent( event ); + } + + SendTeamScoresEvent(); + + GoToIntermission(); + return true; + } + } + + return false; + } + + bool CDODGameRules::CheckWinLimit() + { + // has one team won the specified number of rounds? + + int iWinLimit = mp_winlimit.GetInt(); + + if ( iWinLimit > 0 ) + { + CDODTeam *pAllies = GetGlobalDODTeam(TEAM_ALLIES); + CDODTeam *pAxis = GetGlobalDODTeam(TEAM_AXIS); + + bool bAlliesWin = pAllies->GetRoundsWon() >= iWinLimit; + bool bAxisWin = pAxis->GetRoundsWon() >= iWinLimit; + + if ( bAlliesWin || bAxisWin ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "dod_game_over" ); + if ( event ) + { + event->SetString( "reason", "Reached Round Win Limit" ); + gameeventmanager->FireEvent( event ); + } + + GoToIntermission(); + return true; + } + } + + return false; + } + + void CDODGameRules::CheckPlayerPositions() + { + int i; + bool bUpdatePlayer[MAX_PLAYERS]; + Q_memset( bUpdatePlayer, 0, sizeof(bUpdatePlayer) ); + + // check all players + for ( i=1; i<=gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + Vector origin = pPlayer->GetAbsOrigin(); + + Vector2D pos( (int)(origin.x/4), (int)(origin.y/4) ); + + if ( pos == m_vecPlayerPositions[i-1] ) + continue; // player didn't move enough + + m_vecPlayerPositions[i-1] = pos; + + bUpdatePlayer[i-1] = true; // player position changed since last time + } + + // ok, now send updates to all clients + CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; + + for ( i=1; i<=gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( !pPlayer->IsConnected() ) + continue; + + CSingleUserRecipientFilter filter(pPlayer); + + UserMessageBegin( filter, "UpdateRadar" ); + + playerbits.ClearAll(); + + // see what other players are in it's PVS, don't update them + engine->Message_DetermineMulticastRecipients( false, pPlayer->EyePosition(), playerbits ); + + for ( int i=0; i < gpGlobals->maxClients; i++ ) + { + if ( playerbits.Get(i) ) + continue; // this player is in his PVS, don't update radar pos + + if ( !bUpdatePlayer[i] ) + continue; + + CBasePlayer *pOtherPlayer = UTIL_PlayerByIndex( i+1 ); + + if ( !pOtherPlayer ) + continue; // nothing there + + if ( pOtherPlayer == pPlayer ) + continue; // dont update himself + + if ( !pOtherPlayer->IsAlive() || pOtherPlayer->IsObserver() || !pOtherPlayer->IsConnected() ) + continue; // don't update spectators or dead players + + if ( pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) + { + // update only team mates if not a pure spectator + if ( pPlayer->GetTeamNumber() != pOtherPlayer->GetTeamNumber() ) + continue; + } + + WRITE_BYTE( i+1 ); // player entity index + WRITE_SBITLONG( m_vecPlayerPositions[i].x, COORD_INTEGER_BITS-1 ); + WRITE_SBITLONG( m_vecPlayerPositions[i].y, COORD_INTEGER_BITS-1 ); + WRITE_SBITLONG( AngleNormalize( pOtherPlayer->GetAbsAngles().y ), 9 ); + } + + WRITE_BYTE( 0 ); // end marker + + MessageEnd(); // send message + } + } + + Vector DropToGround( + CBaseEntity *pMainEnt, + const Vector &vPos, + const Vector &vMins, + const Vector &vMaxs ) + { + trace_t trace; + UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace ); + return trace.endpos; + } + + + void TestSpawnPointType( const char *pEntClassName ) + { + // Find the next spawn spot. + CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, pEntClassName ); + + while( pSpot ) + { + // check if pSpot is valid + if( g_pGameRules->IsSpawnPointValid( pSpot, NULL ) ) + { + // the successful spawn point's location + NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 100, 60 ); + + // drop down to ground + Vector GroundPos = DropToGround( NULL, pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX ); + + // the location the player will spawn at + NDebugOverlay::Box( GroundPos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 0, 255, 100, 60 ); + + // draw the spawn angles + QAngle spotAngles = pSpot->GetLocalAngles(); + Vector vecForward; + AngleVectors( spotAngles, &vecForward ); + NDebugOverlay::HorzArrow( pSpot->GetAbsOrigin(), pSpot->GetAbsOrigin() + vecForward * 32, 10, 255, 0, 0, 255, true, 60 ); + } + else + { + // failed spawn point location + NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 100, 60 ); + } + + // increment pSpot + pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); + } + } + + void TestSpawns() + { + TestSpawnPointType( "info_player_allies" ); + TestSpawnPointType( "info_player_axis" ); + } + ConCommand cc_TestSpawns( "map_showspawnpoints", TestSpawns, "Dev - test the spawn points, draws for 60 seconds", FCVAR_CHEAT ); + + CBaseEntity *CDODGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) + { + // get valid spawn point + CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint(); + + // drop down to ground + Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX ); + + // Move the player to the place it said. + pPlayer->Teleport( &GroundPos, &pSpawnSpot->GetLocalAngles(), &vec3_origin ); + pPlayer->m_Local.m_vecPunchAngle = vec3_angle; + + return pSpawnSpot; + } + + // checks if the spot is clear of players + bool CDODGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer ) + { + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return false; + } + + // Check if it is disabled by Enable/Disable + CSpawnPoint *pSpawnPoint = dynamic_cast< CSpawnPoint * >( pSpot ); + if ( pSpawnPoint ) + { + if ( pSpawnPoint->IsDisabled() ) + { + return false; + } + } + + Vector mins = GetViewVectors()->m_vHullMin; + Vector maxs = GetViewVectors()->m_vHullMax; + + Vector vTestMins = pSpot->GetAbsOrigin() + mins; + Vector vTestMaxs = pSpot->GetAbsOrigin() + maxs; + + // First test the starting origin. + return UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs ); + } + + void CDODGameRules::PlayerSpawn( CBasePlayer *p ) + { + CDODPlayer *pPlayer = ToDODPlayer( p ); + + int team = pPlayer->GetTeamNumber(); + + if( team == TEAM_ALLIES || team == TEAM_AXIS ) + { + int iPreviousPlayerClass = pPlayer->m_Shared.PlayerClass(); + + if( pPlayer->m_Shared.DesiredPlayerClass() == PLAYERCLASS_RANDOM ) + { + ChooseRandomClass( pPlayer ); + ClientPrint( pPlayer, HUD_PRINTTALK, "#game_now_as", GetPlayerClassName( pPlayer->m_Shared.PlayerClass(), team ) ); + } + else + { + pPlayer->m_Shared.SetPlayerClass( pPlayer->m_Shared.DesiredPlayerClass() ); + } + + int playerclass = pPlayer->m_Shared.PlayerClass(); + + if ( playerclass != iPreviousPlayerClass ) + { + // spawning as a new class, flush stats + pPlayer->StatEvent_UploadStats(); + } + + if( playerclass != PLAYERCLASS_UNDEFINED ) + { + //Assert( PLAYERCLASS_UNDEFINED < playerclass && playerclass < NUM_PLAYERCLASSES ); + + int i; + + CDODTeam *pTeam = GetGlobalDODTeam( team ); + const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( playerclass ); + + Assert( pClassInfo.m_iTeam == team ); + + pPlayer->SetModel( pClassInfo.m_szPlayerModel ); + pPlayer->SetHitboxSet( 0 ); + + char buf[64]; + int bufsize = sizeof(buf); + + //Give weapons + + // Primary weapon + Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iPrimaryWeapon) ); + CBaseEntity *pPrimaryWpn = pPlayer->GiveNamedItem( buf ); + Assert( pPrimaryWpn ); + + // Secondary weapon + CBaseEntity *pSecondaryWpn = NULL; + if ( pClassInfo.m_iSecondaryWeapon != WEAPON_NONE ) + { + Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iSecondaryWeapon) ); + pSecondaryWpn = pPlayer->GiveNamedItem( buf ); + } + + // Melee weapon + if ( pClassInfo.m_iMeleeWeapon ) + { + Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iMeleeWeapon) ); + pPlayer->GiveNamedItem( buf ); + } + + CWeaponDODBase *pWpn = NULL; + + // Primary Ammo + pWpn = dynamic_cast<CWeaponDODBase *>(pPrimaryWpn); + + if( pWpn ) + { + int iNumClip = pWpn->GetDODWpnData().m_iDefaultAmmoClips - 1; //account for one clip in the gun + int iClipSize = pWpn->GetDODWpnData().iMaxClip1; + pPlayer->GiveAmmo( iNumClip * iClipSize, pWpn->GetDODWpnData().szAmmo1 ); + } + + // Secondary Ammo + if ( pSecondaryWpn ) + { + pWpn = dynamic_cast<CWeaponDODBase *>(pSecondaryWpn); + + if( pWpn ) + { + int iNumClip = pWpn->GetDODWpnData().m_iDefaultAmmoClips - 1; //account for one clip in the gun + int iClipSize = pWpn->GetDODWpnData().iMaxClip1; + pPlayer->GiveAmmo( iNumClip * iClipSize, pWpn->GetDODWpnData().szAmmo1 ); + } + } + + // Grenade Type 1 + if ( pClassInfo.m_iGrenType1 != WEAPON_NONE ) + { + Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iGrenType1) ); + for ( i=0;i<pClassInfo.m_iNumGrensType1;i++ ) + { + pPlayer->GiveNamedItem( buf ); + } + } + + // Grenade Type 2 + if ( pClassInfo.m_iGrenType2 != WEAPON_NONE ) + { + Q_snprintf( buf, bufsize, "weapon_%s", WeaponIDToAlias(pClassInfo.m_iGrenType2) ); + for ( i=0;i<pClassInfo.m_iNumGrensType2;i++ ) + { + pPlayer->GiveNamedItem( buf ); + } + } + + pPlayer->Weapon_Switch( (CBaseCombatWeapon *)pPrimaryWpn ); + + // you get a helmet + pPlayer->SetBodygroup( BODYGROUP_HELMET, pClassInfo.m_iHelmetGroup ); + + // no jumpgear + pPlayer->SetBodygroup( BODYGROUP_JUMPGEAR, BODYGROUP_JUMPGEAR_OFF ); + + pPlayer->SetMaxSpeed( 600 ); + + Assert( playerclass >= 0 && playerclass <= 5 ); + if ( playerclass >= 0 && playerclass <= 5 ) + { + if ( team == TEAM_ALLIES ) + m_iStatsSpawnsPerClass_Allies[playerclass]++; + else if ( team == TEAM_AXIS ) + m_iStatsSpawnsPerClass_Axis[playerclass]++; + } + } + else + { + Assert( !"Player spawning with PLAYERCLASS_UNDEFINED" ); + pPlayer->SetModel( NULL ); + } + } + } + + const char *CDODGameRules::GetPlayerClassName( int cls, int team ) + { + CDODTeam *pTeam = GetGlobalDODTeam( team ); + + if( cls == PLAYERCLASS_RANDOM ) + { + return "#class_random"; + } + + if( cls < 0 || cls >= pTeam->GetNumPlayerClasses() ) + { + Assert( false ); + return NULL; + } + + const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( cls ); + + return pClassInfo.m_szPrintName; + } + + void CDODGameRules::ChooseRandomClass( CDODPlayer *pPlayer ) + { + int i; + int numChoices = 0; + int choices[16]; + int firstclass = 0; + + CDODTeam *pTeam = GetGlobalDODTeam( pPlayer->GetTeamNumber() ); + + int lastclass = pTeam->GetNumPlayerClasses(); + + int previousClass = pPlayer->m_Shared.PlayerClass(); + + // Compile a list of the classes that aren't full + for( i=firstclass;i<lastclass;i++ ) + { + // don't join the same class twice in a row + if ( i == previousClass ) + continue; + + if( CanPlayerJoinClass( pPlayer, i ) ) + { + choices[numChoices] = i; + numChoices++; + } + } + + // If ALL the classes are full + if( numChoices == 0 ) + { + Msg( "Random class found that all classes were full - ignoring class limits for this spawn\n" ); + + pPlayer->m_Shared.SetPlayerClass( random->RandomFloat( firstclass, lastclass ) ); + } + else + { + // Choose a slot randomly + i = random->RandomInt( 0, numChoices-1 ); + + // We are now the class that was in that slot + pPlayer->m_Shared.SetPlayerClass( choices[i] ); + } + } + + //----------------------------------------------------------------------------- + // Purpose: This function can be used to find a valid placement location for an entity. + // Given an origin to start looking from and a minimum radius to place the entity at, + // it will sweep out a circle around vOrigin and try to find a valid spot (on the ground) + // where mins and maxs will fit. + // Input : *pMainEnt - Entity to place + // &vOrigin - Point to search around + // fRadius - Radius to search within + // nTries - Number of tries to attempt + // &mins - mins of the Entity + // &maxs - maxs of the Entity + // &outPos - Return point + // Output : Returns true and fills in outPos if it found a spot. + //----------------------------------------------------------------------------- + bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround ) + { + // This function moves the box out in each dimension in each step trying to find empty space like this: + // + // X + // X X + // Step 1: X Step 2: XXX Step 3: XXXXX + // X X + // X + // + + Vector mins, maxs; + pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs ); + mins -= pMainEnt->GetAbsOrigin(); + maxs -= pMainEnt->GetAbsOrigin(); + + // Put some padding on their bbox. + + Vector vTestMins = mins; + Vector vTestMaxs = maxs; + + // First test the starting origin. + if ( UTIL_IsSpaceEmpty( pMainEnt, vOrigin + vTestMins, vOrigin + vTestMaxs ) ) + { + if ( bDropToGround ) + { + outPos = DropToGround( pMainEnt, vOrigin, vTestMins, vTestMaxs ); + } + else + { + outPos = vOrigin; + } + return true; + } + + Vector vDims = vTestMaxs - vTestMins; + + // Keep branching out until we get too far. + int iCurIteration = 0; + int nMaxIterations = 15; + + int offset = 0; + do + { + for ( int iDim=0; iDim < 2; iDim++ ) + { + float flCurOffset = offset * vDims[iDim]; + + for ( int iSign=0; iSign < 2; iSign++ ) + { + Vector vBase = vOrigin; + vBase[iDim] += (iSign*2-1) * flCurOffset; + + if ( UTIL_IsSpaceEmpty( pMainEnt, vBase + vTestMins, vBase + vTestMaxs ) ) + { + // Ensure that there is a clear line of sight from the spawnpoint entity to the actual spawn point. + // (Useful for keeping things from spawning behind walls near a spawn point) + trace_t tr; + UTIL_TraceLine( vOrigin, vBase, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction != 1.0 ) + { + continue; + } + + if ( bDropToGround ) + outPos = DropToGround( pMainEnt, vBase, vTestMins, vTestMaxs ); + else + outPos = vBase; + + return true; + } + } + } + + ++offset; + } while ( iCurIteration++ < nMaxIterations ); + + // Warning( "EntityPlacementTest for ent %d:%s failed!\n", pMainEnt->entindex(), pMainEnt->GetClassname() ); + return false; + } + + bool CDODGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) + { + //only allow one primary, one secondary and one melee + CWeaponDODBase *pWpn = (CWeaponDODBase *)pWeapon; + + if( pWpn ) + { + int type = pWpn->GetDODWpnData().m_WeaponType; + + switch( type ) + { + case WPN_TYPE_MELEE: + { +#ifdef DEBUG + CWeaponDODBase *pMeleeWeapon = (CWeaponDODBase *)pPlayer->Weapon_GetSlot( WPN_SLOT_MELEE ); + bool bHasMelee = ( pMeleeWeapon != NULL ); + + if( bHasMelee ) + { + Assert( !"Why are we trying to add another melee?" ); + return false; + } +#endif + } + break; + case WPN_TYPE_PISTOL: + case WPN_TYPE_SIDEARM: + { +#ifdef DEBUG + CWeaponDODBase *pSecondaryWeapon = (CWeaponDODBase *)pPlayer->Weapon_GetSlot( WPN_SLOT_SECONDARY ); + bool bHasPistol = ( pSecondaryWeapon != NULL ); + + if( bHasPistol ) + { + Assert( !"Why are we trying to add another pistol?" ); + return false; + } +#endif + } + break; + + case WPN_TYPE_CAMERA: + return true; + + case WPN_TYPE_RIFLE: + case WPN_TYPE_SNIPER: + case WPN_TYPE_SUBMG: + case WPN_TYPE_MG: + case WPN_TYPE_BAZOOKA: + { + //Don't pick up dropped weapons if we have one already + CWeaponDODBase *pPrimaryWeapon = (CWeaponDODBase *)pPlayer->Weapon_GetSlot( WPN_SLOT_PRIMARY ); + bool bHasPrimary = ( pPrimaryWeapon != NULL ); + + if( bHasPrimary ) + return false; + } + break; + + default: + break; + } + } + + return BaseClass::CanHavePlayerItem( pPlayer, pWeapon ); + } + + void CDODGameRules::ResetMapTime( void ) + { + m_flMapResetTime = gpGlobals->curtime; + + // send an event with the time remaining until map change + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_map_time_remaining" ); + if ( event ) + { + event->SetInt( "seconds", GetTimeLeft() ); + gameeventmanager->FireEvent( event ); + } + } + +#endif + +bool CDODGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) +{ + if ( collisionGroup0 > collisionGroup1 ) + { + // swap so that lowest is always first + V_swap(collisionGroup0,collisionGroup1); + } + + //Don't stand on COLLISION_GROUP_WEAPONs + if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT && + collisionGroup1 == COLLISION_GROUP_WEAPON ) + { + return false; + } + + // TE shells don't collide with the player + if ( collisionGroup0 == COLLISION_GROUP_PLAYER && + collisionGroup1 == DOD_COLLISIONGROUP_SHELLS ) + { + return false; + } + + // blocker walls only collide with players + if ( collisionGroup1 == DOD_COLLISIONGROUP_BLOCKERWALL ) + return ( collisionGroup0 == COLLISION_GROUP_PLAYER ) || ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ); + + return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); +} + +int CDODGameRules::GetSubTeam( int team ) +{ + return SUBTEAM_NORMAL; +} + +bool CDODGameRules::IsGameUnderTimeLimit( void ) +{ + return ( mp_timelimit.GetInt() > 0 ); +} + +int CDODGameRules::GetTimeLeft( void ) +{ + float flTimeLimit = mp_timelimit.GetInt() * 60; + + Assert( flTimeLimit > 0 && "Should not call this function when !IsGameUnderTimeLimit" ); + + float flMapChangeTime = m_flMapResetTime + flTimeLimit; + +#ifndef CLIENT_DLL + // If the round timer is longer, let the round complete + if ( m_bUsingTimer && m_pRoundTimer ) + { + float flTimerSeconds = m_pRoundTimer->GetTimeRemaining(); + float flMapChangeSeconds = flMapChangeTime - gpGlobals->curtime; + + // if the map timer is less than the round timer + // AND + // the round timer is less than 2 minutes + + + // If the map time for any reason goes beyond the end of the round, remove the flag + if ( flMapChangeSeconds > flTimerSeconds ) + { + m_bChangeLevelOnRoundEnd = false; + } + else if ( m_bChangeLevelOnRoundEnd || flTimerSeconds < 120 ) + { + // once this happens once in a round, use this until the round ends + // or else the round will end when a team captures an objective and adds time to above 120 + m_bChangeLevelOnRoundEnd = true; + + return (int)( flTimerSeconds ); + } + } +#endif + + return ( (int)(flMapChangeTime - gpGlobals->curtime) ); +} + +int CDODGameRules::GetReinforcementTimerSeconds( int team, float flSpawnEligibleTime ) +{ + // find the first wave that this player can fit in + + float flWaveTime = -1; + + switch( team ) + { + case TEAM_ALLIES: + { + int i = m_iAlliesRespawnHead; + + while( i != m_iAlliesRespawnTail ) + { + if ( flSpawnEligibleTime < m_AlliesRespawnQueue[i] ) + { + flWaveTime = m_AlliesRespawnQueue[i]; + break; + } + + i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE; + } + } + break; + case TEAM_AXIS: + { + int i = m_iAxisRespawnHead; + + while( i != m_iAxisRespawnTail ) + { + if ( flSpawnEligibleTime < m_AxisRespawnQueue[i] ) + { + flWaveTime = m_AxisRespawnQueue[i]; + break; + } + + i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE; + } + } + break; + default: + return -1; + } + + return MAX( 0, (int)( flWaveTime - gpGlobals->curtime ) ); +} + +const CViewVectors* CDODGameRules::GetViewVectors() const +{ + return &g_DODViewVectors; +} + +const CDODViewVectors *CDODGameRules::GetDODViewVectors() const +{ + return &g_DODViewVectors; +} + +#ifndef CLIENT_DLL + + extern ConVar dod_bonusround; + + bool CDODGameRules::IsFriendlyFireOn( void ) + { + // Never friendly fire in bonus round + if ( IsInBonusRound() ) + { + return false; + } + + return friendlyfire.GetBool(); + } + + bool CDODGameRules::IsInBonusRound( void ) + { + return ( dod_bonusround.GetBool() == true && ( State_Get() == STATE_ALLIES_WIN || State_Get() == STATE_AXIS_WIN ) ); + } + + ConVar dod_showroundtransitions( "dod_showroundtransitions", "0", 0, "Show gamestate round transitions" ); + + void CDODGameRules::State_Transition( DODRoundState newState ) + { + State_Leave(); + State_Enter( newState ); + } + + void CDODGameRules::State_Enter( DODRoundState newState ) + { + m_iRoundState = newState; + m_pCurStateInfo = State_LookupInfo( newState ); + + if ( dod_showroundtransitions.GetInt() > 0 ) + { + if ( m_pCurStateInfo ) + Msg( "DODRoundState: entering '%s'\n", m_pCurStateInfo->m_pStateName ); + else + Msg( "DODRoundState: entering #%d\n", newState ); + } + + // Initialize the new state. + if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) + (this->*m_pCurStateInfo->pfnEnterState)(); + } + + void CDODGameRules::State_Leave() + { + if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) + { + (this->*m_pCurStateInfo->pfnLeaveState)(); + } + } + + + void CDODGameRules::State_Think() + { + if ( m_pCurStateInfo && m_pCurStateInfo->pfnThink ) + { + (this->*m_pCurStateInfo->pfnThink)(); + } + } + + + CDODRoundStateInfo* CDODGameRules::State_LookupInfo( DODRoundState state ) + { + static CDODRoundStateInfo playerStateInfos[] = + { + { STATE_INIT, "STATE_INIT", &CDODGameRules::State_Enter_INIT, NULL, &CDODGameRules::State_Think_INIT }, + { STATE_PREGAME, "STATE_PREGAME", &CDODGameRules::State_Enter_PREGAME, NULL, &CDODGameRules::State_Think_PREGAME }, + { STATE_STARTGAME, "STATE_STARTGAME", &CDODGameRules::State_Enter_STARTGAME, NULL, &CDODGameRules::State_Think_STARTGAME }, + { STATE_PREROUND, "STATE_PREROUND", &CDODGameRules::State_Enter_PREROUND, NULL, &CDODGameRules::State_Think_PREROUND }, + { STATE_RND_RUNNING,"STATE_RND_RUNNING",&CDODGameRules::State_Enter_RND_RUNNING, NULL, &CDODGameRules::State_Think_RND_RUNNING }, + { STATE_ALLIES_WIN, "STATE_ALLIES_WIN", &CDODGameRules::State_Enter_ALLIES_WIN, NULL, &CDODGameRules::State_Think_ALLIES_WIN }, + { STATE_AXIS_WIN, "STATE_AXIS_WIN", &CDODGameRules::State_Enter_AXIS_WIN, NULL, &CDODGameRules::State_Think_AXIS_WIN }, + { STATE_RESTART, "STATE_RESTART", &CDODGameRules::State_Enter_RESTART, NULL, &CDODGameRules::State_Think_RESTART }, + { STATE_GAME_OVER, "STATE_GAME_OVER", NULL, NULL, NULL }, + }; + + for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) + { + if ( playerStateInfos[i].m_iRoundState == state ) + return &playerStateInfos[i]; + } + + return NULL; + } + + extern ConVar sv_stopspeed; + extern ConVar sv_friction; + + void CDODGameRules::State_Enter_INIT( void ) + { + InitTeams(); + + sv_stopspeed.SetValue( 50.0f ); + sv_friction.SetValue( 8.0f ); + + ResetMapTime(); + } + + void CDODGameRules::State_Think_INIT( void ) + { + State_Transition( STATE_PREGAME ); + } + + void CDODGameRules::InitTeams( void ) + { + Assert( g_Teams.Count() == 0 ); + + g_Teams.Purge(); // just in case + + // Create the team managers + int i; + for ( i = 0; i < 2; i++ ) // Unassigned and Spectators + { + CTeam *pTeam = static_cast<CTeam*>(CreateEntityByName( "dod_team_manager" )); + pTeam->Init( sTeamNames[i], i ); + + g_Teams.AddToTail( pTeam ); + } + + // clear the player class data + ResetFilePlayerClassInfoDatabase(); + + CTeam *pAllies = static_cast<CTeam*>(CreateEntityByName( "dod_team_allies" )); + Assert( pAllies ); + pAllies->Init( sTeamNames[TEAM_ALLIES], TEAM_ALLIES ); + g_Teams.AddToTail( pAllies ); + + CTeam *pAxis = static_cast<CTeam*>(CreateEntityByName( "dod_team_axis" )); + Assert( pAxis ); + pAxis->Init( sTeamNames[TEAM_AXIS], TEAM_AXIS ); + g_Teams.AddToTail( pAxis ); + } + + // dod_control_point_master can take inputs to add time to the round timer + void CDODGameRules::AddTimerSeconds( int iSecondsToAdd ) + { + if( m_bUsingTimer && m_pRoundTimer ) + { + m_pRoundTimer->AddTimerSeconds( iSecondsToAdd ); + + float flTimerSeconds = m_pRoundTimer->GetTimeRemaining(); + + m_bPlayTimerWarning_1Minute = ( flTimerSeconds > 60 ); + m_bPlayTimerWarning_2Minute = ( flTimerSeconds > 120 ); + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_timer_time_added" ); + if ( event ) + { + event->SetInt( "seconds_added", iSecondsToAdd ); + gameeventmanager->FireEvent( event ); + } + } + } + + int CDODGameRules::GetTimerSeconds( void ) + { + if( m_bUsingTimer && m_pRoundTimer ) + { + return m_pRoundTimer->GetTimeRemaining(); + } + else + { + return 0; + } + } + + // PREGAME - the server is idle and waiting for enough + // players to start up again. When we find an active player + // go to STATE_STARTGAME + void CDODGameRules::State_Enter_PREGAME( void ) + { + m_flNextPeriodicThink = gpGlobals->curtime + 0.1; + + Load_EntText(); + } + + void CDODGameRules::State_Think_PREGAME( void ) + { + CheckLevelInitialized(); + + if( CountActivePlayers() > 0 ) + State_Transition( STATE_STARTGAME ); + } + + // STARTGAME - wait a bit and then spawn everyone into the + // preround + void CDODGameRules::State_Enter_STARTGAME( void ) + { + m_flStateTransitionTime = gpGlobals->curtime + 5 * dod_enableroundwaittime.GetFloat(); + + m_bInitialSpawn = true; + } + + void CDODGameRules::State_Think_STARTGAME() + { + if( gpGlobals->curtime > m_flStateTransitionTime ) + { + if( mp_warmup_time.GetFloat() > 0 ) + { + // go into warmup, reset at end of it + SetInWarmup( true ); + } + + State_Transition( STATE_PREROUND ); + } + } + + void CDODGameRules::State_Enter_PREROUND( void ) + { + // Longer wait time if its the first round, let people join + if ( m_bInitialSpawn ) + { + m_flStateTransitionTime = gpGlobals->curtime + 10 * dod_enableroundwaittime.GetFloat(); + m_bInitialSpawn = false; + } + else + { + m_flStateTransitionTime = gpGlobals->curtime + 5 * dod_enableroundwaittime.GetFloat(); + } + + //Game rules may change, if a new one becomes mastered at the end of the last round + DetectGameRules(); + + //reset everything in the level + RoundRespawn(); + + // reset this now! If its reset at round restart, we lose all the players that died + // during the preround + m_iAlliesRespawnHead = 0; + m_iAlliesRespawnTail = 0; + m_iAxisRespawnHead = 0; + m_iAxisRespawnTail = 0; + m_iNumAlliesRespawnWaves = 0; + m_iNumAxisRespawnWaves = 0; + + m_iLastAlliesCapEvent = CAP_EVENT_NONE; + m_iLastAxisCapEvent = CAP_EVENT_NONE; + + //find all the control points, init the timer + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ); + + if( !pEnt ) + { + Warning( "No dod_control_point_master found in level - control points will not work as expected.\n" ); + } + + bool bFoundTimer = false; + + while( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "RoundInit", NULL, NULL, emptyVariant, 0 ); + + CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( pEnt ); + + if ( pMaster && pMaster->IsActive() ) + { + if ( pMaster->IsUsingRoundTimer() ) + { + bFoundTimer = true; + + m_bUsingTimer = true; + + int iTimerSeconds; + + pMaster->GetTimerData( iTimerSeconds, m_iTimerWinTeam ); + + if ( m_iTimerWinTeam != TEAM_ALLIES && m_iTimerWinTeam != TEAM_AXIS ) + { + Assert( !"Round timer win team can only be allies or axis!\n" ); + } + + // Timer starts paused + if ( !m_pRoundTimer.Get() ) + { + m_pRoundTimer = ( CDODRoundTimer *) CreateEntityByName( "dod_round_timer" ); + } + + Assert( m_pRoundTimer ); + + if ( m_pRoundTimer ) + { + m_pRoundTimer->SetTimeRemaining( iTimerSeconds ); + m_pRoundTimer->PauseTimer(); + + m_bPlayTimerWarning_1Minute = ( iTimerSeconds > 60 ); + m_bPlayTimerWarning_2Minute = ( iTimerSeconds > 120 ); + } + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" ); + } + + if ( bFoundTimer == false ) + { + // No masters are active that require the round timer, destroy it + UTIL_Remove( m_pRoundTimer.Get() ); + m_pRoundTimer = NULL; + } + + //init the cap areas + pEnt = gEntList.FindEntityByClassname( NULL, "dod_capture_area" ); + while( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "RoundInit", NULL, NULL, emptyVariant, 0 ); + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_capture_area" ); + } + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_start" ); + if ( event ) + gameeventmanager->FireEvent( event ); + + // figure out which teams are bombing + m_bAlliesAreBombing = false; + m_bAxisAreBombing = false; + + pEnt = gEntList.FindEntityByClassname( NULL, "dod_bomb_target" ); + while( pEnt ) + { + CDODBombTarget *pTarget = dynamic_cast<CDODBombTarget *>( pEnt ); + + if ( pTarget && pTarget->State_Get() == BOMB_TARGET_ACTIVE ) + { + switch( pTarget->GetBombingTeam() ) + { + case TEAM_ALLIES: + m_bAlliesAreBombing = true; + break; + case TEAM_AXIS: + m_bAxisAreBombing = true; + break; + default: + break; + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" ); + } + } + + void CDODGameRules::State_Think_PREROUND( void ) + { + if( gpGlobals->curtime > m_flStateTransitionTime ) + State_Transition( STATE_RND_RUNNING ); + + CheckRespawnWaves(); + } + + void CDODGameRules::State_Enter_RND_RUNNING( void ) + { + //find all the control points, init the timer + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ); + + while( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "RoundStart", NULL, NULL, emptyVariant, 0 ); + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" ); + } + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_active" ); + if ( event ) + gameeventmanager->FireEvent( event ); + + if( !IsInWarmup() ) + PlayStartRoundVoice(); + + if ( m_bUsingTimer && m_pRoundTimer.Get() != NULL ) + { + m_pRoundTimer->ResumeTimer(); + } + + m_bChangeLevelOnRoundEnd = false; + } + + void CDODGameRules::State_Think_RND_RUNNING( void ) + { + //Where the magic happens + + if ( m_bUsingTimer && m_pRoundTimer ) + { + float flSecondsRemaining = m_pRoundTimer->GetTimeRemaining(); + + if ( flSecondsRemaining <= 0 ) + { + // if there is a bomb still on a timer, and that bomb has + // the potential to add time, then we don't end the game + + bool bBombBlocksWin = false; + + //find all the control points, init the timer + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_bomb_target" ); + + while( pEnt ) + { + CDODBombTarget *pBomb = dynamic_cast<CDODBombTarget *>( pEnt ); + + // Find active bombs that have the potential to add round time + if ( pBomb && pBomb->State_Get() == BOMB_TARGET_ARMED ) + { + if ( pBomb->GetTimerAddSeconds() > 0 ) + { + // don't end the round until this bomb goes off or is disarmed + bBombBlocksWin = true; + break; + } + + CControlPoint *pPoint = pBomb->GetControlPoint(); + int iBombingTeam = pBomb->GetBombingTeam(); + + if ( pPoint && pPoint->GetBombsRemaining() <= 1 ) + { + // find active dod_control_point_masters, ask them if this flag capping + // would end the game + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ); + + while( pEnt ) + { + CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( pEnt ); + + if ( pMaster->IsActive() ) + { + // Check TeamOwnsAllPoints, while overriding this particular flag's owner + if ( pMaster->WouldNewCPOwnerWinGame( pPoint, iBombingTeam ) ) + { + // This bomb may win the game, don't end the round. + bBombBlocksWin = true; + break; + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" ); + } + } + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" ); + } + + if ( bBombBlocksWin == false ) + { + SetWinningTeam( m_iTimerWinTeam ); + + // tell the dod_control_point_master to fire its outputs for the winning team! + // minor hackage - dod_gamerules should be responsible for team win events, not dod_cpm + + //find all the control points, init the timer + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ); + + while( pEnt ) + { + CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( pEnt ); + + if ( pMaster->IsActive() ) + { + pMaster->FireTeamWinOutput( m_iTimerWinTeam ); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point_master" ); + } + } + } + else if ( flSecondsRemaining < 60.0 && m_bPlayTimerWarning_1Minute == true ) + { + // play one minute warning + DevMsg( 1, "Timer Warning: 1 Minute Remaining\n" ); + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_timer_flash" ); + if ( event ) + { + event->SetInt( "time_remaining", 60 ); + gameeventmanager->FireEvent( event ); + } + + m_bPlayTimerWarning_1Minute = false; + } + else if ( flSecondsRemaining < 120.0 && m_bPlayTimerWarning_2Minute == true ) + { + // play two minute warning + DevMsg( 1, "Timer Warning: 2 Minutes Remaining\n" ); + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_timer_flash" ); + if ( event ) + { + event->SetInt( "time_remaining", 120 ); + gameeventmanager->FireEvent( event ); + } + + m_bPlayTimerWarning_2Minute = false; + } + } + + //if we don't find any active players, return to STATE_PREGAME + if( CountActivePlayers() <= 0 ) + { + State_Transition( STATE_PREGAME ); + return; + } + + CheckRespawnWaves(); + + // check round restart + if( m_flRestartRoundTime > 0 && m_flRestartRoundTime < gpGlobals->curtime ) + { + // time to restart! + State_Transition( STATE_RESTART ); + m_flRestartRoundTime = -1; + } + + // check ready restart + if( m_bAwaitingReadyRestart && m_bHeardAlliesReady && m_bHeardAxisReady ) + { + //State_Transition( STATE_RESTART ); + m_flRestartRoundTime = gpGlobals->curtime + 5; + m_bAwaitingReadyRestart = false; + } + } + + void CDODGameRules::CheckRespawnWaves( void ) + { + bool bDoFailSafeWaveCheck = false; + + if ( m_flNextFailSafeWaveCheckTime < gpGlobals->curtime ) + { + bDoFailSafeWaveCheck = true; + m_flNextFailSafeWaveCheckTime = gpGlobals->curtime + 3.0; + } + + //Respawn Timers + if( m_iNumAlliesRespawnWaves > 0 ) + { + if ( m_AlliesRespawnQueue[m_iAlliesRespawnHead] < gpGlobals->curtime ) + { + DevMsg( "Wave: Respawning Allies\n" ); + + RespawnTeam( TEAM_ALLIES ); + + PopWaveTime( TEAM_ALLIES ); + } + } + else if ( bDoFailSafeWaveCheck ) + { + // if there are any allied people waiting to spawn, spawn them + FailSafeSpawnPlayersOnTeam( TEAM_ALLIES ); + } + + if( m_iNumAxisRespawnWaves > 0 ) + { + if ( m_AxisRespawnQueue[m_iAxisRespawnHead] < gpGlobals->curtime ) + { + DevMsg( "Wave: Respawning Axis\n" ); + + RespawnTeam( TEAM_AXIS ); + + PopWaveTime( TEAM_AXIS ); + } + } + else if ( bDoFailSafeWaveCheck ) + { + // if there are any axis people waiting to spawn, spawn them + FailSafeSpawnPlayersOnTeam( TEAM_AXIS ); + } + } + + void CDODGameRules::FailSafeSpawnPlayersOnTeam( int iTeam ) + { + DODRoundState roundState = State_Get(); + + CDODTeam *pTeam = GetGlobalDODTeam( iTeam ); + if ( pTeam ) + { + int iNumPlayers = pTeam->GetNumPlayers(); + for ( int i=0;i<iNumPlayers;i++ ) + { + CDODPlayer *pPlayer = pTeam->GetDODPlayer(i); + if ( !pPlayer ) + continue; + + // if this player is waiting to spawn, spawn them + + if ( pPlayer->IsAlive() ) + continue; + + if( pPlayer->m_Shared.DesiredPlayerClass() == PLAYERCLASS_UNDEFINED ) + continue; + + if ( gpGlobals->curtime < ( pPlayer->GetDeathTime() + DEATH_CAM_TIME ) ) + continue; + + if ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) + continue; + + if ( roundState != STATE_PREROUND && pPlayer->State_Get() == STATE_DEATH_ANIM ) + continue; + + // Respawn this player + pPlayer->DODRespawn(); + + Assert( !"This will happen, but see if we can figure out why we get here" ); + } + } + } + + //ALLIES WIN + void CDODGameRules::State_Enter_ALLIES_WIN( void ) + { + float flTime = MAX( 5, dod_bonusroundtime.GetFloat() ); + + m_flStateTransitionTime = gpGlobals->curtime + flTime * dod_enableroundwaittime.GetFloat(); + + if ( m_bUsingTimer && m_pRoundTimer ) + { + m_pRoundTimer->PauseTimer(); + } + } + + void CDODGameRules::State_Think_ALLIES_WIN( void ) + { + if( gpGlobals->curtime > m_flStateTransitionTime ) + { + State_Transition( STATE_PREROUND ); + } + } + + //AXIS WIN + void CDODGameRules::State_Enter_AXIS_WIN( void ) + { + float flTime = MAX( 5, dod_bonusroundtime.GetFloat() ); + + m_flStateTransitionTime = gpGlobals->curtime + flTime * dod_enableroundwaittime.GetFloat(); + + if ( m_bUsingTimer && m_pRoundTimer ) + { + m_pRoundTimer->PauseTimer(); + } + } + + void CDODGameRules::State_Think_AXIS_WIN( void ) + { + if( gpGlobals->curtime > m_flStateTransitionTime ) + { + State_Transition( STATE_PREROUND ); + } + } + + // manual restart + void CDODGameRules::State_Enter_RESTART( void ) + { + // send scores + SendTeamScoresEvent(); + + // send restart event + IGameEvent *event = gameeventmanager->CreateEvent( "dod_restart_round" ); + if ( event ) + gameeventmanager->FireEvent( event ); + + SetInWarmup( false ); + + ResetScores(); + + // reset the round time + ResetMapTime(); + + State_Transition( STATE_PREROUND ); + } + + void CDODGameRules::SendTeamScoresEvent( void ) + { + // send scores + IGameEvent *event = gameeventmanager->CreateEvent( "dod_team_scores" ); + + if ( event ) + { + CDODTeam *pAllies = GetGlobalDODTeam( TEAM_ALLIES ); + CDODTeam *pAxis = GetGlobalDODTeam( TEAM_AXIS ); + + Assert( pAllies && pAxis ); + + event->SetInt( "allies_caps", pAllies->GetRoundsWon() ); + event->SetInt( "allies_tick", pAllies->GetScore() ); + event->SetInt( "allies_players", pAllies->GetNumPlayers() ); + event->SetInt( "axis_caps", pAxis->GetRoundsWon() ); + event->SetInt( "axis_tick", pAxis->GetScore() ); + event->SetInt( "axis_players", pAxis->GetNumPlayers() ); + + gameeventmanager->FireEvent( event ); + } + } + + void CDODGameRules::State_Think_RESTART( void ) + { + Assert( 0 ); // should never get here, State_Enter_RESTART sets us into a different state + } + + void CDODGameRules::ResetScores( void ) + { + GetGlobalDODTeam( TEAM_ALLIES )->ResetScores(); + GetGlobalDODTeam( TEAM_AXIS )->ResetScores(); + + CDODPlayer *pDODPlayer; + + for( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pDODPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if (pDODPlayer == NULL) + continue; + + if (FNullEnt( pDODPlayer->edict() )) + continue; + + pDODPlayer->ResetScores(); + } + } + + ConVar dod_showcleanedupents( "dod_showcleanedupents", "0", 0, "Show entities that are removed on round respawn" ); + + // Utility function + bool FindInList( const char **pStrings, const char *pToFind ) + { + int i = 0; + while ( pStrings[i][0] != 0 ) + { + if ( Q_stricmp( pStrings[i], pToFind ) == 0 ) + return true; + i++; + } + + return false; + } + + void CDODGameRules::CleanUpMap() + { + // Recreate all the map entities from the map data (preserving their indices), + // then remove everything else except the players. + + if( dod_showcleanedupents.GetBool() ) + { + Msg( "CleanUpMap\n===============\n" ); + } + + // Get rid of all entities except players. + CBaseEntity *pCur = gEntList.FirstEnt(); + while ( pCur ) + { + if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) + { + if( dod_showcleanedupents.GetBool() ) + { + Msg( "Removed Entity: %s\n", pCur->GetClassname() ); + } + UTIL_Remove( pCur ); + } + + pCur = gEntList.NextEnt( pCur ); + } + + // Really remove the entities so we can have access to their slots below. + gEntList.CleanupDeleteList(); + + // Now reload the map entities. + class CDODMapEntityFilter : public IMapEntityFilter + { + public: + virtual bool ShouldCreateEntity( const char *pClassname ) + { + // Don't recreate the preserved entities. + if ( !FindInList( s_PreserveEnts, pClassname ) ) + { + return true; + } + else + { + // Increment our iterator since it's not going to call CreateNextEntity for this ent. + if ( m_iIterator != g_MapEntityRefs.InvalidIndex() ) + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); + + return false; + } + } + + + virtual CBaseEntity* CreateNextEntity( const char *pClassname ) + { + if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) + { + // This shouldn't be possible. When we loaded the map, it should have used + // CDODMapLoadEntityFilter, which should have built the g_MapEntityRefs list + // with the same list of entities we're referring to here. + Assert( false ); + return NULL; + } + else + { + CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity. + + if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) ) + { + // Doh! The entity was delete and its slot was reused. + // Just use any old edict slot. This case sucks because we lose the baseline. + return CreateEntityByName( pClassname ); + } + else + { + // Cool, the slot where this entity was is free again (most likely, the entity was + // freed above). Now create an entity with this specific index. + return CreateEntityByName( pClassname, ref.m_iEdict ); + } + } + } + + public: + int m_iIterator; // Iterator into g_MapEntityRefs. + }; + CDODMapEntityFilter filter; + filter.m_iIterator = g_MapEntityRefs.Head(); + + // DO NOT CALL SPAWN ON info_node ENTITIES! + + MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); + } + + int CDODGameRules::CountActivePlayers( void ) + { + int i; + int count = 0; + CDODPlayer *pDODPlayer; + + for (i = 1; i <= gpGlobals->maxClients; i++ ) + { + pDODPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if( pDODPlayer ) + { + if( pDODPlayer->IsReadyToPlay() ) + { + count++; + } + } + } + + return count; + } + + void CDODGameRules::RoundRespawn( void ) + { + CleanUpMap(); + RespawnAllPlayers(); + + // reset per-round scores for each player + for ( int i=0;i<MAX_PLAYERS;i++ ) + { + CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer ) + { + pPlayer->ResetPerRoundStats(); + } + } + } + + typedef struct { + int iPlayerIndex; + int iScore; + } playerscore_t; + + int PlayerScoreInfoSort( const playerscore_t *p1, const playerscore_t *p2 ) + { + // check frags + if ( p1->iScore > p2->iScore ) + return -1; + if ( p2->iScore > p1->iScore ) + return 1; + + // check index + if ( p1->iPlayerIndex < p2->iPlayerIndex ) + return -1; + + return 1; + } + + // Store which event happened most recently, flag cap or bomb explode + void CDODGameRules::CapEvent( int event, int team ) + { + switch( team ) + { + case TEAM_ALLIES: + m_iLastAlliesCapEvent = event; + break; + case TEAM_AXIS: + m_iLastAxisCapEvent = event; + break; + } + } + + void FillEventCategory( IGameEvent *event, int side, int category, CUtlVector<playerscore_t> &pList ) + { + switch ( side ) + { + case 0: + event->SetInt( "category_left", category ); + break; + case 1: + event->SetInt( "category_right", category ); + break; + } + + static const char *pCategoryNames[2][6] = + { + { + "left_1", + "left_score_1", + "left_2", + "left_score_2", + "left_3", + "left_score_3" + }, + { + "right_1", + "right_score_1", + "right_2", + "right_score_2", + "right_3", + "right_score_3" + } + }; + + int iNumInList = pList.Count(); + + if ( iNumInList > 0 ) + { + event->SetInt( pCategoryNames[side][0], pList[0].iPlayerIndex ); + event->SetInt( pCategoryNames[side][1], pList[0].iScore ); + } + else + event->SetInt( pCategoryNames[side][0], 0 ); + + if ( iNumInList > 1 ) + { + event->SetInt( pCategoryNames[side][2], pList[1].iPlayerIndex ); + event->SetInt( pCategoryNames[side][3], pList[1].iScore ); + } + else + event->SetInt( pCategoryNames[side][2], 0 ); + + if ( iNumInList > 2 ) + { + event->SetInt( pCategoryNames[side][4], pList[2].iPlayerIndex ); + event->SetInt( pCategoryNames[side][5], pList[2].iScore ); + } + else + event->SetInt( pCategoryNames[side][4], 0 ); + } + + //Input for other entities to declare a round winner. + //Most often a dod_control_point_master saying that the + //round timer expired or that someone capped all the flags + void CDODGameRules::SetWinningTeam( int team ) + { + if ( team != TEAM_ALLIES && team != TEAM_AXIS ) + { + Assert( !"bad winning team set" ); + return; + } + + PlayWinSong(team); + + GetGlobalDODTeam( team )->IncrementRoundsWon(); + + switch(team) + { + case TEAM_ALLIES: + { + State_Transition( STATE_ALLIES_WIN ); + } + break; + case TEAM_AXIS: + { + State_Transition( STATE_AXIS_WIN ); + } + break; + default: + break; + } + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_win" ); + if ( event ) + { + event->SetInt( "team", team ); + gameeventmanager->FireEvent( event ); + } + + // if this was in colmar, and the losing team did not cap any points, + // the winners may have gotten an achievement + if ( FStrEq( STRING(gpGlobals->mapname), "dod_colmar" ) ) + { + CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster *>( gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ) ); + + if ( pMaster ) + { + // 1. losing team must not own any control points + // 2. for each point that the winning team owns, that takes bombs, it must still have 2 bombs required + bool bFlawlessVictory = true; + + int iNumCP = pMaster->GetNumPoints(); + + for ( int i=0;i<iNumCP;i++ ) + { + CControlPoint *pPoint = pMaster->GetCPByIndex(i); + + if ( !pPoint || !pPoint->PointIsVisible() ) + continue; + + // if the enemy owns any visible points, not a flawless victory + if ( pPoint->GetOwner() != team ) + { + bFlawlessVictory = false; + } + + // 0 bombs remaining means we blew it up and now own it. + // 1 bomb remaining means we own it, but the other team blew it up a bit. + else if ( pPoint->GetBombsRequired() > 0 && pPoint->GetBombsRemaining() == 1 ) + { + bFlawlessVictory = false; + } + } + + if ( bFlawlessVictory ) + { + GetGlobalDODTeam( team )->AwardAchievement( ACHIEVEMENT_DOD_COLMAR_DEFENSE ); + } + } + } + + // send team scores + SendTeamScoresEvent(); + + IGameEvent *winEvent = gameeventmanager->CreateEvent( "dod_win_panel" ); + + if ( winEvent ) + { + // determine what categories to send + + if ( m_bUsingTimer ) + { + if ( team == m_iTimerWinTeam ) + { + // timer expired, defenders win + // show total time that was defended + winEvent->SetBool( "show_timer_defend", true ); + winEvent->SetInt( "timer_time", m_pRoundTimer->GetTimerMaxLength() ); + } + else + { + // attackers win + // show time it took for them to win + winEvent->SetBool( "show_timer_attack", true ); + + int iTimeElapsed = m_pRoundTimer->GetTimerMaxLength() - (int)m_pRoundTimer->GetTimeRemaining(); + winEvent->SetInt( "timer_time", iTimeElapsed ); + } + } + else + { + winEvent->SetBool( "show_timer_attack", false ); + winEvent->SetBool( "show_timer_defend", false ); + } + + int iLastEvent = ( team == TEAM_ALLIES ) ? m_iLastAlliesCapEvent : m_iLastAxisCapEvent; + + winEvent->SetInt( "final_event", iLastEvent ); + + int i; + int index; + + CUtlVector<playerscore_t> m_TopCappers; + CUtlVector<playerscore_t> m_TopDefenders; + CUtlVector<playerscore_t> m_TopBombers; + CUtlVector<playerscore_t> m_TopKills; + + CDODTeam *pWinningTeam = GetGlobalDODTeam( team ); + + int iNumPlayers = pWinningTeam->GetNumPlayers(); + + for ( i=0;i<iNumPlayers;i++ ) + { + CDODPlayer *pPlayer = dynamic_cast<CDODPlayer *>( pWinningTeam->GetPlayer(i) ); + + if ( pPlayer ) + { + int iCaps = pPlayer->GetPerRoundCaps(); + if ( iCaps ) + { + index = m_TopCappers.AddToTail(); + m_TopCappers[index].iPlayerIndex = pPlayer->entindex(); + m_TopCappers[index].iScore = iCaps; + } + + int iDefenses = pPlayer->GetPerRoundDefenses(); + if ( iDefenses ) + { + index = m_TopDefenders.AddToTail(); + m_TopDefenders[index].iPlayerIndex = pPlayer->entindex(); + m_TopDefenders[index].iScore = iDefenses; + } + + // bombs + int iBombsDetonated = pPlayer->GetPerRoundBombsDetonated(); + if ( iBombsDetonated ) + { + index = m_TopBombers.AddToTail(); + m_TopBombers[index].iPlayerIndex = pPlayer->entindex(); + m_TopBombers[index].iScore = iBombsDetonated; + } + + // kills + int iKills = pPlayer->GetPerRoundKills(); + if ( iKills ) + { + index = m_TopKills.AddToTail(); + m_TopKills[index].iPlayerIndex = pPlayer->entindex(); + m_TopKills[index].iScore = iKills; + } + + pPlayer->StatEvent_RoundWin(); + } + } + + CDODTeam *pLosingTeam = GetGlobalDODTeam( ( team == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES ); + + iNumPlayers = pLosingTeam->GetNumPlayers(); + + for ( i=0;i<iNumPlayers;i++ ) + { + CDODPlayer *pPlayer = dynamic_cast<CDODPlayer *>( pLosingTeam->GetPlayer(i) ); + + if ( pPlayer ) + { + pPlayer->StatEvent_RoundLoss(); + } + } + + m_TopCappers.Sort( PlayerScoreInfoSort ); + m_TopDefenders.Sort( PlayerScoreInfoSort ); + m_TopBombers.Sort( PlayerScoreInfoSort ); + m_TopKills.Sort( PlayerScoreInfoSort ); + + // Decide what two categories to show in the winpanel + // based on the gametype and which event have good information + // to show + + int iCategoryPriority[8]; + int pos = 0; + + // Default is to show two blank sides + iCategoryPriority[pos++] = WINPANEL_TOP3_NONE; + iCategoryPriority[pos++] = WINPANEL_TOP3_NONE; + + // Only show a category if it has information in it + if ( m_TopKills.Count() > 0 ) + { + iCategoryPriority[pos++] = WINPANEL_TOP3_KILLERS; + } + + if ( m_TopDefenders.Count() > 0 ) + { + iCategoryPriority[pos++] = WINPANEL_TOP3_DEFENDERS; + } + + if ( m_TopBombers.Count() > 0 ) + { + iCategoryPriority[pos++] = WINPANEL_TOP3_BOMBERS; + } + else if ( m_TopCappers.Count() > 0 ) + { + iCategoryPriority[pos++] = WINPANEL_TOP3_CAPPERS; + } + + // Get the two most interesting + int iLeftCategory = iCategoryPriority[pos-1]; + int iRightCategory = iCategoryPriority[pos-2]; + + switch( iLeftCategory ) + { + case WINPANEL_TOP3_BOMBERS: + FillEventCategory( winEvent, 0, iLeftCategory, m_TopBombers ); + break; + case WINPANEL_TOP3_CAPPERS: + FillEventCategory( winEvent, 0, iLeftCategory, m_TopCappers ); + break; + case WINPANEL_TOP3_DEFENDERS: + FillEventCategory( winEvent, 0, iLeftCategory, m_TopDefenders ); + break; + case WINPANEL_TOP3_KILLERS: + FillEventCategory( winEvent, 0, iLeftCategory, m_TopKills ); + break; + case WINPANEL_TOP3_NONE: + default: + break; + } + + switch( iRightCategory ) + { + case WINPANEL_TOP3_BOMBERS: + FillEventCategory( winEvent, 1, iRightCategory, m_TopBombers ); + break; + case WINPANEL_TOP3_CAPPERS: + FillEventCategory( winEvent, 1, iRightCategory, m_TopCappers ); + break; + case WINPANEL_TOP3_DEFENDERS: + FillEventCategory( winEvent, 1, iRightCategory, m_TopDefenders ); + break; + case WINPANEL_TOP3_KILLERS: + FillEventCategory( winEvent, 1, iRightCategory, m_TopKills ); + break; + case WINPANEL_TOP3_NONE: + default: + break; + } + + gameeventmanager->FireEvent( winEvent ); + } + } + + void TestWinpanel( void ) + { + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_round_win" ); + event->SetInt( "team", TEAM_ALLIES ); + + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + IGameEvent *event2 = gameeventmanager->CreateEvent( "dod_point_captured" ); + if ( event2 ) + { + char cappers[9]; // pCappingPlayers is max length 8 + int i; + for( i=0;i<1;i++ ) + { + cappers[i] = (char)1; + } + + cappers[i] = '\0'; + + // pCappingPlayers is a null terminated list of player indeces + event2->SetString( "cappers", cappers ); + event2->SetBool( "bomb", true ); + + gameeventmanager->FireEvent( event2 ); + } + + IGameEvent *winEvent = gameeventmanager->CreateEvent( "dod_win_panel" ); + + if ( winEvent ) + { + if ( 1 ) + { + if ( 0 /*team == m_iTimerWinTeam */) + { + // timer expired, defenders win + // show total time that was defended + winEvent->SetBool( "show_timer_defend", true ); + winEvent->SetInt( "timer_time", 0 /*m_pRoundTimer->GetTimerMaxLength() */); + } + else + { + // attackers win + // show time it took for them to win + winEvent->SetBool( "show_timer_attack", true ); + + int iTimeElapsed = 90; //m_pRoundTimer->GetTimerMaxLength() - (int)m_pRoundTimer->GetTimeRemaining(); + winEvent->SetInt( "timer_time", iTimeElapsed ); + } + } + else + { + winEvent->SetBool( "show_timer_attack", false ); + winEvent->SetBool( "show_timer_defend", false ); + } + + int iLastEvent = CAP_EVENT_FLAG; + + winEvent->SetInt( "final_event", iLastEvent ); + + CUtlVector<playerscore_t> m_TopKillers; + CUtlVector<playerscore_t> m_TopDefenders; + CUtlVector<playerscore_t> m_TopCappers; + CUtlVector<playerscore_t> m_TopBombers; + + CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex(1) ); + + if ( !pPlayer ) + return; + + int i = 0; + int index; + for ( i=0;i<3;i++ ) + { + index = m_TopCappers.AddToTail(); + m_TopCappers[index].iPlayerIndex = pPlayer->entindex(); + m_TopCappers[index].iScore = pPlayer->GetPerRoundCaps() + 1; + + index = m_TopDefenders.AddToTail(); + m_TopDefenders[index].iPlayerIndex = pPlayer->entindex(); + m_TopDefenders[index].iScore = pPlayer->GetPerRoundDefenses() + 1; + + index = m_TopBombers.AddToTail(); + m_TopBombers[index].iPlayerIndex = pPlayer->entindex(); + m_TopBombers[index].iScore = pPlayer->GetPerRoundBombsDetonated() + 1; + + index = m_TopKillers.AddToTail(); + m_TopKillers[index].iPlayerIndex = pPlayer->entindex(); + m_TopKillers[index].iScore = pPlayer->GetPerRoundKills() + 1; + } + + m_TopCappers.Sort( PlayerScoreInfoSort ); + m_TopDefenders.Sort( PlayerScoreInfoSort ); + + //FillEventCategory( winEvent, 0, WINPANEL_TOP3_KILLERS, m_TopKillers ); + //FillEventCategory( winEvent, 1, WINPANEL_TOP3_DEFENDERS, m_TopDefenders ); + FillEventCategory( winEvent, 0, WINPANEL_TOP3_BOMBERS, m_TopBombers ); + FillEventCategory( winEvent, 1, WINPANEL_TOP3_CAPPERS, m_TopCappers ); + + gameeventmanager->FireEvent( winEvent ); + } + } + ConCommand dod_test_winpanel( "dod_test_winpanel", TestWinpanel, "", FCVAR_CHEAT ); + + // bForceRespawn - respawn player even if dead or dying + // bTeam - if true, only respawn the passed team + // iTeam - team to respawn + void CDODGameRules::RespawnPlayers( bool bForceRespawn, bool bTeam /* = false */, int iTeam/* = TEAM_UNASSIGNED */ ) + { + if ( bTeam ) + { + if ( iTeam == TEAM_ALLIES ) + DevMsg( 2, "Respawning Allies\n" ); + else if ( iTeam == TEAM_AXIS ) + DevMsg( 2, "Respawning Axis\n" ); + else + Assert(!"Trying to respawn a strange team"); + } + + CDODPlayer *pPlayer; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if ( !pPlayer ) + continue; + + // Check for team specific spawn + if ( bTeam && pPlayer->GetTeamNumber() != iTeam ) + continue; + + // players that haven't chosen a class can never spawn + if( pPlayer->m_Shared.DesiredPlayerClass() == PLAYERCLASS_UNDEFINED ) + { + ClientPrint(pPlayer, HUD_PRINTTALK, "#game_will_spawn"); + continue; + } + + // If we aren't force respawning, don't respawn players that: + // - are alive + // - are still in the death anim stage of dying + if ( !bForceRespawn ) + { + if ( pPlayer->IsAlive() ) + continue; + + if ( gpGlobals->curtime < ( pPlayer->GetDeathTime() + DEATH_CAM_TIME ) ) + continue; + + if ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) + continue; + + if ( State_Get() != STATE_PREROUND && pPlayer->State_Get() == STATE_DEATH_ANIM ) + continue; + } + + // Respawn this player + pPlayer->DODRespawn(); + } + } + + bool CDODGameRules::IsPlayerClassOnTeam( int cls, int team ) + { + if( cls == PLAYERCLASS_RANDOM ) + return true; + + CDODTeam *pTeam = GetGlobalDODTeam( team ); + + return ( cls >= 0 && cls < pTeam->GetNumPlayerClasses() ); + } + + bool CDODGameRules::CanPlayerJoinClass( CDODPlayer *pPlayer, int cls ) + { + if( cls == PLAYERCLASS_RANDOM ) + { + return mp_allowrandomclass.GetBool(); + } + + if( ReachedClassLimit( pPlayer->GetTeamNumber(), cls ) ) + return false; + + return true; + } + + bool CDODGameRules::ReachedClassLimit( int team, int cls ) + { + Assert( cls != PLAYERCLASS_UNDEFINED ); + Assert( cls != PLAYERCLASS_RANDOM ); + + // get the cvar + int iClassLimit = GetClassLimit( team, cls ); + + // count how many are active + int iClassExisting = CountPlayerClass( team, cls ); + + CDODTeam *pTeam = GetGlobalDODTeam( team ); + const CDODPlayerClassInfo &pThisClassInfo = pTeam->GetPlayerClassInfo( cls ); + + if( mp_combinemglimits.GetBool() && pThisClassInfo.m_bClassLimitMGMerge ) + { + // find the other classes that have "mergemgclasses" + + for( int i=0; i<pTeam->GetNumPlayerClasses();i++ ) + { + if( i != cls ) + { + const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( i ); + if( pClassInfo.m_bClassLimitMGMerge ) + { + // add that class' limits and counts + iClassLimit += GetClassLimit( team, i ); + iClassExisting += CountPlayerClass( team, i ); + } + } + } + } + + if( iClassLimit > -1 && iClassExisting >= iClassLimit ) + { + return true; + } + + return false; + } + + int CDODGameRules::CountPlayerClass( int team, int cls ) + { + int num = 0; + CDODPlayer *pDODPlayer; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pDODPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if (pDODPlayer == NULL) + continue; + + if (FNullEnt( pDODPlayer->edict() )) + continue; + + if( pDODPlayer->GetTeamNumber() != team ) + continue; + + if( pDODPlayer->m_Shared.DesiredPlayerClass() == cls ) + num++; + } + + return num; + } + + int CDODGameRules::GetClassLimit( int team, int cls ) + { + CDODTeam *pTeam = GetGlobalDODTeam( team ); + + Assert( pTeam ); + + const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( cls ); + + int iClassLimit; + + ConVar *pLimitCvar = ( ConVar * )cvar->FindVar( pClassInfo.m_szLimitCvar ); + + Assert( pLimitCvar ); + + if( pLimitCvar ) + iClassLimit = pLimitCvar->GetInt(); + else + iClassLimit = -1; + + return iClassLimit; + } + + void CDODGameRules::CheckLevelInitialized() + { + if ( !m_bLevelInitialized ) + { + // Count the number of spawn points for each team + // This determines the maximum number of players allowed on each + + CBaseEntity* ent = NULL; + + m_iSpawnPointCount_Allies = 0; + m_iSpawnPointCount_Axis = 0; + + while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_allies" ) ) != NULL ) + { + if ( IsSpawnPointValid( ent, NULL ) ) + { + m_iSpawnPointCount_Allies++; + + // store in a list + m_AlliesSpawnPoints.AddToTail( ent ); + } + else + { + Warning("Invalid allies spawnpoint at (%.1f,%.1f,%.1f)\n", + ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] ); + } + } + + while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_axis" ) ) != NULL ) + { + if ( IsSpawnPointValid( ent, NULL ) ) + { + m_iSpawnPointCount_Axis++; + + // store in a list + m_AxisSpawnPoints.AddToTail( ent ); + } + else + { + Warning("Invalid axis spawnpoint at (%.1f,%.1f,%.1f)\n", + ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] ); + } + } + + m_bLevelInitialized = true; + } + } + + CUtlVector<EHANDLE> *CDODGameRules::GetSpawnPointListForTeam( int iTeam ) + { + switch ( iTeam ) + { + case TEAM_ALLIES: + return &m_AlliesSpawnPoints; + case TEAM_AXIS: + return &m_AxisSpawnPoints; + default: + break; + } + + return NULL; + } + + /* create some proxy entities that we use for transmitting data */ + void CDODGameRules::CreateStandardEntities() + { + // Create the player resource + g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "dod_player_manager", vec3_origin, vec3_angle ); + + // Create the objective resource + g_pObjectiveResource = (CDODObjectiveResource *)CBaseEntity::Create( "dod_objective_resource", vec3_origin, vec3_angle ); + + Assert( g_pObjectiveResource ); + + // Create the entity that will send our data to the client. +#ifdef DBGFLAG_ASSERT + CBaseEntity *pEnt = +#endif + CBaseEntity::Create( "dod_gamerules", vec3_origin, vec3_angle ); + Assert( pEnt ); + } + + ConVar dod_waverespawnfactor( "dod_waverespawnfactor", "1.0", FCVAR_REPLICATED | FCVAR_CHEAT, "Factor for respawn wave timers" ); + + float CDODGameRules::GetWaveTime( int iTeam ) + { + float flRespawnTime = 0.0f; + + switch( iTeam ) + { + case TEAM_ALLIES: + flRespawnTime = ( m_iNumAlliesRespawnWaves > 0 ) ? m_AlliesRespawnQueue[m_iAlliesRespawnHead] : -1; + break; + case TEAM_AXIS: + flRespawnTime = ( m_iNumAxisRespawnWaves > 0 ) ? m_AxisRespawnQueue[m_iAxisRespawnHead] : -1; + break; + default: + Assert( !"Why are you trying to get the wave time for a non-team?" ); + break; + } + + return flRespawnTime; + } + + float CDODGameRules::GetMaxWaveTime( int nTeam ) + { + float fTime = 0; + + // Quick waves to get everyone in if we are in PREROUND + if ( State_Get() == STATE_PREROUND ) + { + return 1.0; + } + + int nNumPlayers = GetGlobalDODTeam( nTeam )->GetNumPlayers(); + + if( nNumPlayers < 3 ) + fTime = 6.f; + else if( nNumPlayers < 6 ) + fTime = 8.f; + else if( nNumPlayers < 8 ) + fTime = 10.f; + else if( nNumPlayers < 10 ) + fTime = 11.f; + else if( nNumPlayers < 12 ) + fTime = 12.f; + else if( nNumPlayers < 14 ) + fTime = 13.f; + else + fTime = 14.f; + + //adjust wave time based on mapper settings + //they can adjust the factor ( default 1.0 ) + // to give longer or shorter wait times for + // either team + if( nTeam == TEAM_ALLIES ) + fTime *= m_GamePlayRules.m_fAlliesRespawnFactor; + else if( nTeam == TEAM_AXIS ) + fTime *= m_GamePlayRules.m_fAxisRespawnFactor; + + // Finally, adjust the respawn time based on how well the team is doing + // a team with more flags should respawn faster. + // Give a bonus to respawn time for each flag that we own that we + // don't own by default. + + CControlPointMaster *pMaster = dynamic_cast<CControlPointMaster*>( gEntList.FindEntityByClassname( NULL, "dod_control_point_master" ) ); + + if( pMaster ) + { + int advantageFlags = pMaster->CountAdvantageFlags( nTeam ); + + // this can be negative if we are losing, this will add time! + + fTime -= (float)(advantageFlags) * dod_flagrespawnbonus.GetFloat(); + } + + fTime *= dod_waverespawnfactor.GetFloat(); + + // Minimum 5 seconds + if (fTime <= DEATH_CAM_TIME) + fTime = DEATH_CAM_TIME; + + // Maximum 20 seconds + if ( fTime > MAX_WAVE_RESPAWN_TIME ) + { + fTime = MAX_WAVE_RESPAWN_TIME; + } + + return fTime; + } + + + void CDODGameRules::CreateOrJoinRespawnWave( CDODPlayer *pPlayer ) + { + int team = pPlayer->GetTeamNumber(); + float flWaveTime = GetWaveTime( team ) - gpGlobals->curtime; + + if( flWaveTime <= 0 ) + { + // start a new wave + + DevMsg( "Wave: Starting a new wave for team %d, time %.1f\n", team, GetMaxWaveTime(team) ); + + //start a new wave with this player + AddWaveTime( team, GetMaxWaveTime(team) ); + } + else + { + // see if this player needs to start a new wave + + int team = pPlayer->GetTeamNumber(); + float flSpawnEligibleTime = gpGlobals->curtime + DEATH_CAM_TIME; + + if ( team == TEAM_ALLIES ) + { + bool bFoundWave = false; + + int i = m_iAlliesRespawnHead; + + while( i != m_iAlliesRespawnTail ) + { + // if the player can fit in this wave, set bFound = true + if ( flSpawnEligibleTime < m_AlliesRespawnQueue[i] ) + { + bFoundWave = true; + break; + } + + i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE; + } + + if ( !bFoundWave ) + { + // add a new wave to the end + AddWaveTime( TEAM_ALLIES, GetMaxWaveTime(TEAM_ALLIES) ); + } + } + else if ( team == TEAM_AXIS ) + { + bool bFoundWave = false; + + int i = m_iAxisRespawnHead; + + while( i != m_iAxisRespawnTail ) + { + // if the player can fit in this wave, set bFound = true + if ( flSpawnEligibleTime < m_AxisRespawnQueue[i] ) + { + bFoundWave = true; + break; + } + + i = ( i+1 ) % DOD_RESPAWN_QUEUE_SIZE; + } + + if ( !bFoundWave ) + { + // add a new wave to the end + AddWaveTime( TEAM_AXIS, GetMaxWaveTime(TEAM_AXIS) ); + } + } + else + Assert( 0 ); + } + } + + bool CDODGameRules::InRoundRestart( void ) + { + if ( State_Get() == STATE_PREROUND ) + return true; + + return false; + } + + void CDODGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) + { + CDODPlayer *pDODVictim = ToDODPlayer( pVictim ); + + // if you're still playing dod, you know how this works, let's not + // interfere with the freezecam panel + //bool bPlayed = pDODVictim->HintMessage( HINT_PLAYER_KILLED_WAVETIME ); + + // If we already played the killed hint, play the deathcam hint + //if ( !bPlayed ) + //{ + // pDODVictim->HintMessage( HINT_DEATHCAM ); + //} + + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CDODPlayer *pScorer = ToDODPlayer( GetDeathScorer( pKiller, pInflictor ) ); + + if( pScorer && pScorer->IsPlayer() && pScorer != pVictim ) + { + if( pVictim->GetTeamNumber() == pScorer->GetTeamNumber() ) + { + pScorer->HintMessage( HINT_FRIEND_KILLED, true ); //force this + } + else + { + pScorer->HintMessage( HINT_ENEMY_KILLED ); + } + } + + // determine if this kill affected a nemesis relationship + int iDeathFlags = 0; + if ( pScorer ) + { + CalcDominationAndRevenge( pScorer, pDODVictim, &iDeathFlags ); + } + + pDODVictim->SetDeathFlags( iDeathFlags ); // for deathnotice I assume? + + DeathNotice( pVictim, info ); + + // dvsents2: uncomment when removing all FireTargets + // variant_t value; + // g_EventQueue.AddEvent( "game_playerdie", "Use", value, 0, pVictim, pVictim ); + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + + bool bScoring = !IsInWarmup(); + + if( bScoring ) + { + pVictim->IncrementDeathCount( 1 ); + } + + // Did the player kill himself? + if ( pVictim == pScorer ) + { + // Players lose a frag for killing themselves + //if( bScoring ) + // pVictim->IncrementFragCount( -1 ); + } + else if ( pScorer ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + if( bScoring ) + pScorer->IncrementFragCount( DODPointsForKill( pVictim, info ) ); + + // Allow the scorer to immediately paint a decal + pScorer->AllowImmediateDecalPainting(); + + // dvsents2: uncomment when removing all FireTargets + //variant_t value; + //g_EventQueue.AddEvent( "game_playerkill", "Use", value, 0, pScorer, pScorer ); + FireTargets( "game_playerkill", pScorer, pScorer, USE_TOGGLE, 0 ); + + // see if this saved a capture + if ( pDODVictim->m_signals.GetState() & SIGNAL_CAPTUREAREA ) + { + //find the area the player is in and see if his death causes a block + CAreaCapture *pArea = dynamic_cast<CAreaCapture *>(gEntList.FindEntityByClassname( NULL, "dod_capture_area" ) ); + while( pArea ) + { + if ( pArea->CheckIfDeathCausesBlock( pDODVictim, pScorer ) ) + { + break; + } + + pArea = dynamic_cast<CAreaCapture *>( gEntList.FindEntityByClassname( pArea, "dod_capture_area" ) ); + } + } + if ( pDODVictim->m_bIsDefusing && pDODVictim->m_pDefuseTarget && pScorer->GetTeamNumber() != pDODVictim->GetTeamNumber() ) + { + CDODBombTarget *pTarget = pDODVictim->m_pDefuseTarget; + + pTarget->DefuseBlocked( pScorer ); + + IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_defuser" ); + if ( event ) + { + event->SetInt( "userid", pScorer->GetUserID() ); + event->SetInt( "victimid", pDODVictim->GetUserID() ); + + gameeventmanager->FireEvent( event ); + } + } + } + else + { + // Players lose a frag for letting the world kill them + //if( bScoring ) + // pVictim->IncrementFragCount( -1 ); + } + } + + void CDODGameRules::DetectGameRules( void ) + { + bool bFound = false; + + CBaseEntity *pEnt = NULL; + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_doddetect" ); + + while( pEnt ) + { + CDODDetect *pDetect = dynamic_cast<CDODDetect *>(pEnt); + + if( pDetect && pDetect->IsMasteredOn() ) + { + CDODGamePlayRules *pRules = pDetect->GetGamePlay(); + Assert( pRules ); + CopyGamePlayLogic( *pRules ); + bFound = true; + break; + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "info_doddetect" ); + } + + if( !bFound ) + { + m_GamePlayRules.Reset(); + } + } + + void CDODGameRules::PlayWinSong( int team ) + { + switch(team) + { + case TEAM_ALLIES: + BroadcastSound( "Game.USWin" ); + break; + case TEAM_AXIS: + BroadcastSound( "Game.GermanWin" ); + break; + default: + Assert(0); + break; + } + } + + void CDODGameRules::BroadcastSound( const char *sound ) + { + //send it to everyone + IGameEvent *event = gameeventmanager->CreateEvent( "dod_broadcast_audio" ); + if ( event ) + { + event->SetString( "sound", sound ); + gameeventmanager->FireEvent( event ); + } + } + + void CDODGameRules::PlayStartRoundVoice( void ) + { + // One for the Allies.. + switch( m_GamePlayRules.m_iAlliesStartRoundVoice ) + { + case STARTROUND_ATTACK: + PlaySpawnSoundToTeam( "Voice.US_ObjectivesAttack", TEAM_ALLIES ); + break; + + case STARTROUND_DEFEND: + PlaySpawnSoundToTeam( "Voice.US_ObjectivesDefend", TEAM_ALLIES ); + break; + + case STARTROUND_BEACH: + PlaySpawnSoundToTeam( "Voice.US_Beach", TEAM_ALLIES ); + break; + + case STARTROUND_ATTACK_TIMED: + PlaySpawnSoundToTeam( "Voice.US_ObjectivesAttackTimed", TEAM_ALLIES ); + break; + + case STARTROUND_DEFEND_TIMED: + PlaySpawnSoundToTeam( "Voice.US_ObjectivesDefendTimed", TEAM_ALLIES ); + break; + + case STARTROUND_FLAGS: + default: + PlaySpawnSoundToTeam( "Voice.US_Flags", TEAM_ALLIES ); + break; + } + + // and one for the Axis + switch( m_GamePlayRules.m_iAxisStartRoundVoice ) + { + case STARTROUND_ATTACK: + PlaySpawnSoundToTeam( "Voice.German_ObjectivesAttack", TEAM_AXIS ); + break; + + case STARTROUND_DEFEND: + PlaySpawnSoundToTeam( "Voice.German_ObjectivesDefend", TEAM_AXIS ); + break; + + case STARTROUND_BEACH: + PlaySpawnSoundToTeam( "Voice.German_Beach", TEAM_AXIS ); + break; + + case STARTROUND_ATTACK_TIMED: + PlaySpawnSoundToTeam( "Voice.German_ObjectivesAttackTimed", TEAM_AXIS ); + break; + + case STARTROUND_DEFEND_TIMED: + PlaySpawnSoundToTeam( "Voice.German_ObjectivesDefendTimed", TEAM_AXIS ); + break; + + case STARTROUND_FLAGS: + default: + PlaySpawnSoundToTeam( "Voice.German_Flags", TEAM_AXIS ); + break; + } + } + + void CDODGameRules::PlaySpawnSoundToTeam( const char *sound, int team ) + { + // find the first valid player and make them do it as a voice command + CDODPlayer *pPlayer; + + static int iLastSpeaker = 1; + + int iCurrent = iLastSpeaker; + + bool bBreakLoop = false; + + while( !bBreakLoop ) + { + iCurrent++; + if( iCurrent > gpGlobals->maxClients ) + iCurrent = 1; + + if( iCurrent == iLastSpeaker ) + { + // couldn't find a different player. check the same player again + // and then break regardless + bBreakLoop = true; + } + + pPlayer = ToDODPlayer( UTIL_PlayerByIndex( iCurrent ) ); + + if (pPlayer == NULL) + continue; + + if (FNullEnt( pPlayer->edict() )) + continue; + + if( pPlayer && pPlayer->GetTeamNumber() == team && pPlayer->IsAlive() ) + { + CPASFilter filter( pPlayer->WorldSpaceCenter() ); + pPlayer->EmitSound( filter, pPlayer->entindex(), sound ); + + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_HANDSIGNAL ); + + iLastSpeaker = iCurrent; + break; + } + } + } + + void CDODGameRules::ClientDisconnected( edict_t *pClient ) + { + CDODPlayer *pPlayer = ToDODPlayer( GetContainingEntity( pClient ) ); + + if( pPlayer ) + { + pPlayer->DestroyRagdoll(); + + pPlayer->StatEvent_UploadStats(); + } + + // Tally the latest time for this player + pPlayer->TallyLatestTimePlayedPerClass( pPlayer->GetTeamNumber(), pPlayer->m_Shared.DesiredPlayerClass() ); + + pPlayer->RemoveNemesisRelationships(); + + for( int j=0;j<7;j++ ) + { + m_flSecondsPlayedPerClass_Allies[j] += pPlayer->m_flTimePlayedPerClass_Allies[j]; + m_flSecondsPlayedPerClass_Axis[j] += pPlayer->m_flTimePlayedPerClass_Axis[j]; + } + + int iPlayerIndex = pPlayer->entindex(); + Assert( iPlayerIndex >= 1 && iPlayerIndex <= MAX_PLAYERS); + if ( iPlayerIndex >= 1 && iPlayerIndex <= MAX_PLAYERS ) + { + // for every other player, set all all the kills with respect to this player to 0 + for ( int i = 1; i <= MAX_PLAYERS; i++ ) + { + CDODPlayer *p = ToDODPlayer( UTIL_PlayerByIndex(i) ); + if ( !p ) + continue; + + p->iNumKilledByUnanswered[iPlayerIndex] = 0; + } + } + + BaseClass::ClientDisconnected( pClient ); + } + + void CDODGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) + { + // Work out what killed the player, and send a message to all clients about it + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_ID = 0; + + // Find the killer & the scorer + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CDODPlayer *pScorer = ToDODPlayer( GetDeathScorer( pKiller, pInflictor ) ); + CDODPlayer *pDODVictim = ToDODPlayer( pVictim ); + + Assert( pDODVictim ); + + if ( pScorer ) // Is the killer a client? + { + killer_ID = pScorer->GetUserID(); + + if ( pInflictor ) + { + if ( pInflictor == pScorer ) + { + CWeaponDODBase *pWeapon = pScorer->GetActiveDODWeapon(); + + if ( pWeapon ) + { + int iWeaponType = pWeapon->GetDODWpnData().m_WeaponType; + + // Putting this here because we already have the weapon pointer. + if ( iWeaponType == WPN_TYPE_MG && pScorer->GetTeamNumber() != pVictim->GetTeamNumber() ) + { + CDODBipodWeapon *pMG = dynamic_cast<CDODBipodWeapon *>( pWeapon ); + Assert( pMG ); + if ( pMG->IsDeployed() ) + { + pScorer->HandleDeployedMGKillCount( 1 ); + } + } + + // if the weapon does not belong to the same team + if ( pWeapon->GetDODWpnData().m_iDefaultTeam != pScorer->GetTeamNumber() && + pScorer->GetTeamNumber() != pVictim->GetTeamNumber() ) + { + pScorer->HandleEnemyWeaponsAchievement( 1 ); + } + + // achievement for getting kills with several different weapon types in one life + if ( pScorer->GetTeamNumber() != pVictim->GetTeamNumber() ) + { + pScorer->HandleComboWeaponKill( iWeaponType ); + } + + if( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK ) + { + //it was a butt or bayonet! + killer_weapon_name = pWeapon->GetSecondaryDeathNoticeName(); + } + // If the inflictor is the killer, then it must be their current weapon doing the damage + else + { + killer_weapon_name = pWeapon->GetClassname(); + } + } + } + else + { + killer_weapon_name = STRING( pInflictor->m_iClassname ); // it's just that easy + } + } + } + else + { + killer_weapon_name = STRING( pInflictor->m_iClassname ); + } + + // strip the NPC_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + { + killer_weapon_name += 7; + } + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + { + killer_weapon_name += 5; + } + else if ( strncmp( killer_weapon_name, "rocket_", 7 ) == 0 ) + { + killer_weapon_name += 7; + } + else if ( strncmp( killer_weapon_name, "grenade_", 8 ) == 0 ) + { + killer_weapon_name += 8; + + // achievement for getting kills with several different weapon types in one life + if ( pScorer && pScorer->GetTeamNumber() != pVictim->GetTeamNumber() ) + { + pScorer->HandleComboWeaponKill( WPN_TYPE_GRENADE ); + } + } + + IGameEvent *event = gameeventmanager->CreateEvent( "player_death" ); + + if ( event ) + { + event->SetInt("userid", pVictim->GetUserID() ); + event->SetInt("attacker", killer_ID ); + event->SetString("weapon", killer_weapon_name ); + event->SetInt("priority", 7 ); + + if ( pDODVictim->GetDeathFlags() & DOD_DEATHFLAG_DOMINATION ) + { + event->SetInt( "dominated", 1 ); + } + if ( pDODVictim->GetDeathFlags() & DOD_DEATHFLAG_REVENGE ) + { + event->SetInt( "revenge", 1 ); + } + + gameeventmanager->FireEvent( event ); + } + } + + + // CDODDetect - map entity for mappers to choose game rules + LINK_ENTITY_TO_CLASS( info_doddetect, CDODDetect ); + + CDODDetect::CDODDetect() + { + m_GamePlayRules.Reset(); + } + + void CDODDetect::Spawn( void ) + { + SetSolid( SOLID_NONE ); + + BaseClass::Spawn(); + } + + bool CDODDetect::IsMasteredOn( void ) + { + //For now return true + return true; + } + + bool CDODDetect::KeyValue( const char *szKeyName, const char *szValue ) + { + if (FStrEq(szKeyName, "detect_allies_respawnfactor")) + { + m_GamePlayRules.m_fAlliesRespawnFactor = atof(szValue); + } + else if (FStrEq(szKeyName, "detect_axis_respawnfactor")) + { + m_GamePlayRules.m_fAxisRespawnFactor = atof(szValue); + } + else if (FStrEq(szKeyName, "detect_allies_startroundvoice")) + { + m_GamePlayRules.m_iAlliesStartRoundVoice = atoi(szValue); + } + else if (FStrEq(szKeyName, "detect_axis_startroundvoice")) + { + m_GamePlayRules.m_iAxisStartRoundVoice = atof(szValue); + } + else + return CBaseEntity::KeyValue( szKeyName, szValue ); + + return true; + } + + //checks to see if the desired team is stacked, returns true if it is + bool CDODGameRules::TeamStacked( int iNewTeam, int iCurTeam ) + { + //players are allowed to change to their own team + if(iNewTeam == iCurTeam) + return false; + + int iTeamLimit = mp_limitteams.GetInt(); + + // Tabulate the number of players on each team. + int iNumAllies = GetGlobalTeam( TEAM_ALLIES )->GetNumPlayers(); + int iNumAxis = GetGlobalTeam( TEAM_AXIS )->GetNumPlayers(); + + switch ( iNewTeam ) + { + case TEAM_ALLIES: + if( iCurTeam != TEAM_UNASSIGNED && iCurTeam != TEAM_SPECTATOR ) + { + if((iNumAllies + 1) > (iNumAxis + iTeamLimit - 1)) + return true; + else + return false; + } + else + { + if((iNumAllies + 1) > (iNumAxis + iTeamLimit)) + return true; + else + return false; + } + break; + case TEAM_AXIS: + if( iCurTeam != TEAM_UNASSIGNED && iCurTeam != TEAM_SPECTATOR ) + { + if((iNumAxis + 1) > (iNumAllies + iTeamLimit - 1)) + return true; + else + return false; + } + else + { + if((iNumAxis + 1) > (iNumAllies + iTeamLimit)) + return true; + else + return false; + } + break; + } + + return false; + } + + // Falling damage stuff. + #define DOD_PLAYER_FATAL_FALL_SPEED 900 // approx 60 feet + #define DOD_PLAYER_MAX_SAFE_FALL_SPEED 500 // approx 20 feet + #define DOD_DAMAGE_FOR_FALL_SPEED ((float)100 / ( DOD_PLAYER_FATAL_FALL_SPEED - DOD_PLAYER_MAX_SAFE_FALL_SPEED )) // damage per unit per second. + + /* + #define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + */ + + float CDODGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) + { + pPlayer->m_Local.m_flFallVelocity -= DOD_PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_Local.m_flFallVelocity * DOD_DAMAGE_FOR_FALL_SPEED; + } + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Init CS ammo definitions +//----------------------------------------------------------------------------- + +// shared ammo definition +// JAY: Trying to make a more physical bullet response +#define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) +#define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) + +// exaggerate all of the forces, but use real numbers to keep them consistent +#define BULLET_IMPULSE_EXAGGERATION 1 + +// convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s +#define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) + +CAmmoDef* GetAmmoDef() +{ + static CAmmoDef def; + static bool bInitted = false; + + if ( !bInitted ) + { + bInitted = true; + + //pistol ammo + def.AddAmmoType( DOD_AMMO_COLT, DMG_BULLET, TRACER_NONE, 0, 0, 21, 5000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_P38, DMG_BULLET, TRACER_NONE, 0, 0, 24, 5000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_C96, DMG_BULLET, TRACER_NONE, 0, 0, 60, 5000, 10, 14 ); + + //rifles + def.AddAmmoType( DOD_AMMO_GARAND, DMG_BULLET, TRACER_NONE, 0, 0, 88, 9000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_K98, DMG_BULLET, TRACER_NONE, 0, 0, 65, 9000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_M1CARBINE, DMG_BULLET, TRACER_NONE, 0, 0, 165, 9000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_SPRING, DMG_BULLET, TRACER_NONE, 0, 0, 55, 9000, 10, 14 ); + + //submg + def.AddAmmoType( DOD_AMMO_SUBMG, DMG_BULLET, TRACER_NONE, 0, 0, 210, 7000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_BAR, DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 260, 9000, 10, 14 ); + + //mg + def.AddAmmoType( DOD_AMMO_30CAL, DMG_BULLET | DMG_MACHINEGUN, TRACER_LINE_AND_WHIZ, 0, 0, 300, 9000, 10, 14 ); + def.AddAmmoType( DOD_AMMO_MG42, DMG_BULLET | DMG_MACHINEGUN, TRACER_LINE_AND_WHIZ, 0, 0, 500, 9000, 10, 14 ); + + //rockets + def.AddAmmoType( DOD_AMMO_ROCKET, DMG_BLAST, TRACER_NONE, 0, 0, 5, 9000, 10, 14 ); + + //grenades + def.AddAmmoType( DOD_AMMO_HANDGRENADE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_STICKGRENADE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_HANDGRENADE_EX, DMG_BLAST, TRACER_NONE, 0, 0, 1, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_STICKGRENADE_EX, DMG_BLAST, TRACER_NONE, 0, 0, 1, 1, 4, 8 ); + + // smoke grenades + def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_US, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_GER, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_US_LIVE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_SMOKEGRENADE_GER_LIVE,DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + + // rifle grenades + def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_US, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_GER, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_US_LIVE, DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + def.AddAmmoType( DOD_AMMO_RIFLEGRENADE_GER_LIVE,DMG_BLAST, TRACER_NONE, 0, 0, 2, 1, 4, 8 ); + } + + return &def; +} + +#ifndef CLIENT_DLL +void CDODGameRules::AddWaveTime( int team, float flTime ) +{ + switch ( team ) + { + case TEAM_ALLIES: + { + Assert( m_iNumAlliesRespawnWaves < DOD_RESPAWN_QUEUE_SIZE ); + + if ( m_iNumAlliesRespawnWaves >= DOD_RESPAWN_QUEUE_SIZE ) + { + Warning( "Trying to add too many allies respawn waves\n" ); + return; + } + + m_AlliesRespawnQueue.Set( m_iAlliesRespawnTail, gpGlobals->curtime + flTime ); + m_iNumAlliesRespawnWaves++; + + m_iAlliesRespawnTail = ( m_iAlliesRespawnTail + 1 ) % DOD_RESPAWN_QUEUE_SIZE; + + DevMsg( 1, "AddWaveTime ALLIES head %d tail %d numtotal %d time %.1f\n", + m_iAlliesRespawnHead.Get(), + m_iAlliesRespawnTail.Get(), + m_iNumAlliesRespawnWaves, + gpGlobals->curtime + flTime ); + } + break; + case TEAM_AXIS: + { + Assert( m_iNumAxisRespawnWaves < DOD_RESPAWN_QUEUE_SIZE ); + + if ( m_iNumAxisRespawnWaves >= DOD_RESPAWN_QUEUE_SIZE ) + { + Warning( "Trying to add too many axis respawn waves\n" ); + return; + } + + m_AxisRespawnQueue.Set( m_iAxisRespawnTail, gpGlobals->curtime + flTime ); + m_iNumAxisRespawnWaves++; + + m_iAxisRespawnTail = ( m_iAxisRespawnTail + 1 ) % DOD_RESPAWN_QUEUE_SIZE; + + DevMsg( 1, "AddWaveTime AXIS head %d tail %d numtotal %d time %.1f\n", + m_iAxisRespawnHead.Get(), + m_iAxisRespawnTail.Get(), + m_iNumAxisRespawnWaves, + gpGlobals->curtime + flTime ); + } + break; + default: + Assert(0); + break; + } +} + +void CDODGameRules::PopWaveTime( int team ) +{ + switch ( team ) + { + case TEAM_ALLIES: + { + Assert( m_iNumAlliesRespawnWaves > 0 ); + + m_iAlliesRespawnHead = ( m_iAlliesRespawnHead + 1 ) % DOD_RESPAWN_QUEUE_SIZE; + m_iNumAlliesRespawnWaves--; + + DevMsg( 1, "PopWaveTime ALLIES head %d tail %d numtotal %d time %.1f\n", + m_iAlliesRespawnHead.Get(), + m_iAlliesRespawnTail.Get(), + m_iNumAlliesRespawnWaves, + gpGlobals->curtime ); + } + break; + case TEAM_AXIS: + { + Assert( m_iNumAxisRespawnWaves > 0 ); + + m_iAxisRespawnHead = ( m_iAxisRespawnHead + 1 ) % DOD_RESPAWN_QUEUE_SIZE; + m_iNumAxisRespawnWaves--; + + DevMsg( 1, "PopWaveTime AXIS head %d tail %d numtotal %d time %.1f\n", + m_iAxisRespawnHead.Get(), + m_iAxisRespawnTail.Get(), + m_iNumAxisRespawnWaves, + gpGlobals->curtime ); + } + break; + default: + Assert(0); + break; + } +} + +#endif + + +#ifndef CLIENT_DLL + +const char *CDODGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer ) +{ + char *pszPrefix = ""; + + if ( !pPlayer ) // dedicated server output + { + pszPrefix = ""; + } + else + { + // don't show dead prefix if in the bonus round or at round end + // because we can chat at these times. + bool bShowDeadPrefix = ( pPlayer->IsAlive() == false ) && !IsInBonusRound() && + ( State_Get() != STATE_GAME_OVER ); + + if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) + { + return ""; + } + + if ( bTeamOnly ) + { + if ( bShowDeadPrefix ) + { + pszPrefix = "(Dead)(Team)"; //#chatprefix_deadteam"; + } + else + { + //MATTTODO: localize chat prefixes + pszPrefix = "(Team)"; //"#chatprefix_team"; + } + } + // everyone + else + { + if ( bShowDeadPrefix ) + { + pszPrefix = "(Dead)"; //"#chatprefix_dead"; + } + } + } + + return pszPrefix; +} + +void CDODGameRules::ClientSettingsChanged( CBasePlayer *pPlayer ) +{ + CDODPlayer *pDODPlayer = ToDODPlayer( pPlayer ); + + Assert( pDODPlayer ); + + pDODPlayer->SetAutoReload( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autoreload" ) ) > 0 ); + pDODPlayer->SetShowHints( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_showhelp" ) ) > 0 ); + pDODPlayer->SetAutoRezoom( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autorezoom" ) ) > 0 ); + + BaseClass::ClientSettingsChanged( pPlayer ); +} + +//----------------------------------------------------------------------------- +// Purpose: Determines if attacker and victim have gotten domination or revenge +//----------------------------------------------------------------------------- +void CDODGameRules::CalcDominationAndRevenge( CDODPlayer *pAttacker, CDODPlayer *pVictim, int *piDeathFlags ) +{ + // team kills don't count + if ( pAttacker->GetTeamNumber() == pVictim->GetTeamNumber() ) + return; + + int iKillsUnanswered = ++(pVictim->iNumKilledByUnanswered[pAttacker->entindex()]); + + pAttacker->iNumKilledByUnanswered[pVictim->entindex()] = 0; + + if ( DOD_KILLS_DOMINATION == iKillsUnanswered ) + { + // this is the Nth unanswered kill between killer and victim, killer is now dominating victim + *piDeathFlags |= DOD_DEATHFLAG_DOMINATION; + + // set victim to be dominated by killer + pAttacker->m_Shared.SetPlayerDominated( pVictim, true ); + + pAttacker->StatEvent_ScoredDomination(); + } + else if ( pVictim->m_Shared.IsPlayerDominated( pAttacker->entindex() ) ) + { + // the killer killed someone who was dominating him, gains revenge + *piDeathFlags |= DOD_DEATHFLAG_REVENGE; + + // set victim to no longer be dominating the killer + pVictim->m_Shared.SetPlayerDominated( pAttacker, false ); + + pAttacker->StatEvent_ScoredRevenge(); + } +} + +int CDODGameRules::DODPointsForKill( CBasePlayer *pVictim, const CTakeDamageInfo &info ) +{ + if ( IsInWarmup() ) + return 0; + + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CDODPlayer *pScorer = ToDODPlayer( GetDeathScorer( pKiller, pInflictor ) ); + + // Don't give -1 points for killing a teammate with the bomb. + // It was their fault for standing too close really. + if ( pVictim->GetTeamNumber() == pScorer->GetTeamNumber() && + info.GetDamageType() & DMG_BOMB ) + { + return 0; + } + + return BaseClass::IPointsForKill( pScorer, pVictim ); + +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the weapon in the player's inventory that would be better than +// the given weapon. +// Note, this version allows us to switch to a weapon that has no ammo as a last +// resort. +//----------------------------------------------------------------------------- +CBaseCombatWeapon *CDODGameRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) +{ + CBaseCombatWeapon *pCheck; + CBaseCombatWeapon *pBest;// this will be used in the event that we don't find a weapon in the same category. + + int iCurrentWeight = -1; + int iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + // If I have a weapon, make sure I'm allowed to holster it + if ( pCurrentWeapon ) + { + if ( !pCurrentWeapon->AllowsAutoSwitchFrom() || !pCurrentWeapon->CanHolster() ) + { + // Either this weapon doesn't allow autoswitching away from it or I + // can't put this weapon away right now, so I can't switch. + return NULL; + } + + iCurrentWeight = pCurrentWeapon->GetWeight(); + } + + for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i ) + { + pCheck = pPlayer->GetWeapon( i ); + if ( !pCheck ) + continue; + + // If we have an active weapon and this weapon doesn't allow autoswitching away + // from another weapon, skip it. + if ( pCurrentWeapon && !pCheck->AllowsAutoSwitchTo() ) + continue; + + int iWeight = pCheck->GetWeight(); + + // Empty weapons are lowest priority + if ( !pCheck->HasAnyAmmo() ) + { + iWeight = 0; + } + + if ( iWeight > -1 && iWeight == iCurrentWeight && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pPlayer->Weapon_CanSwitchTo( pCheck ) ) + { + return pCheck; + } + } + else if ( iWeight > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //Msg( "Considering %s\n", STRING( pCheck->GetClassname() ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->GetWeight(); + pBest = pCheck; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + return pBest; +} + +char *szHitgroupNames[] = +{ + "generic", + "head", + "chest", + "stomach", + "arm_left", + "arm_right", + "leg_left", + "leg_right" +}; + +void CDODGameRules::WriteStatsFile( const char *pszLogName ) +{ + int i, j, k; + + FileHandle_t hFile = filesystem->Open( pszLogName, "w" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + { + Warning( "Helper_LoadFile: missing %s\n", pszLogName ); + return; + } + + // Header + filesystem->FPrintf( hFile, "<?xml version=\"1.0\" ?>\n\n" ); + + // open stats + filesystem->FPrintf( hFile, "<stats>\n" ); + + // per player + for ( i=0;i<MAX_PLAYERS;i++ ) + { + CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer ) + { + filesystem->FPrintf( hFile, "\t<player>\n" ); + + filesystem->FPrintf( hFile, "\t\t<playername>%s</playername>", pPlayer->GetPlayerName() ); + filesystem->FPrintf( hFile, "\t\t<steamid>STEAM:0:01</steamid>" ); + + //float flTimePlayed = gpGlobals->curtime - pPlayer->m_flConnectTime; + //filesystem->FPrintf( hFile, "\t\t<time_played>%.1f</time_played>\n", flTimePlayed ); + + /* + pPlayer->TallyLatestTimePlayedPerClass( pPlayer->GetTeamNumber(), pPlayer->m_Shared.DesiredPlayerClass() ); + + filesystem->FPrintf( hFile, "\t\t<time_played_per_class>\n" ); + for( j=0;j<7;j++ ) + { + // TODO : add real class names + filesystem->FPrintf( hFile, "\t\t\t<class%i>%.1f</class%i>\n", + j, + pPlayer->m_flTimePlayedPerClass[j], + j ); + } + filesystem->FPrintf( hFile, "\t\t</time_played_per_class>\n" ); + */ + + filesystem->FPrintf( hFile, "\t\t<area_captures>%i</area_captures>\n", pPlayer->m_iNumAreaCaptures ); + filesystem->FPrintf( hFile, "\t\t<area_defenses>%i</area_defenses>\n", pPlayer->m_iNumAreaDefenses ); + filesystem->FPrintf( hFile, "\t\t<bonus_round_kills>%i</bonus_round_kills>\n", pPlayer->m_iNumBonusRoundKills ); + + for ( j=0;j<MAX_WEAPONS;j++ ) + { + if ( pPlayer->m_WeaponStats[j].m_iNumShotsTaken > 0 ) + { + filesystem->FPrintf( hFile, "\t\t<weapon>\n" ); + + // weapon id + // weapon name + + filesystem->FPrintf( hFile, "\t\t\t<weaponname>%s</weaponname>\n", WeaponIDToAlias( j ) ); + filesystem->FPrintf( hFile, "\t\t\t<weaponid>%i</weaponid>\n", j ); + filesystem->FPrintf( hFile, "\t\t\t<shots>%i</shots>\n", pPlayer->m_WeaponStats[j].m_iNumShotsTaken ); + filesystem->FPrintf( hFile, "\t\t\t<hits>%i</hits>\n", pPlayer->m_WeaponStats[j].m_iNumShotsHit ); + filesystem->FPrintf( hFile, "\t\t\t<damage>%i</damage>\n", pPlayer->m_WeaponStats[j].m_iTotalDamageGiven ); + filesystem->FPrintf( hFile, "\t\t\t<avgdist>%.1f</avgdist>\n", pPlayer->m_WeaponStats[j].m_flAverageHitDistance ); + filesystem->FPrintf( hFile, "\t\t\t<kills>%i</kills>\n", pPlayer->m_WeaponStats[j].m_iNumKills ); + + filesystem->FPrintf( hFile, "\t\t\t<hitgroups_hit>\n" ); + for( k=0;k<8;k++ ) + { + if ( pPlayer->m_WeaponStats[j].m_iBodygroupsHit[k] > 0 ) + { + filesystem->FPrintf( hFile, "\t\t\t\t<%s>%i</%s>\n", + szHitgroupNames[k], + pPlayer->m_WeaponStats[j].m_iBodygroupsHit[k], + szHitgroupNames[k] ); + } + } + filesystem->FPrintf( hFile, "\t\t\t</hitgroups_hit>\n" ); + + filesystem->FPrintf( hFile, "\t\t\t<times_hit>%i</times_hit>\n", pPlayer->m_WeaponStats[j].m_iNumHitsTaken ); + filesystem->FPrintf( hFile, "\t\t\t<damage_taken>%i</damage_taken>\n", pPlayer->m_WeaponStats[j].m_iTotalDamageTaken ); + filesystem->FPrintf( hFile, "\t\t\t<times_killed>%i</times_killed>\n", pPlayer->m_WeaponStats[j].m_iTimesKilled ); + + filesystem->FPrintf( hFile, "\t\t\t<hit_in_hitgroups>\n" ); + for( k=0;k<8;k++ ) + { + if ( pPlayer->m_WeaponStats[j].m_iHitInBodygroups[k] > 0 ) + { + filesystem->FPrintf( hFile, "\t\t\t\t<%s>%i</%s>\n", + szHitgroupNames[k], + pPlayer->m_WeaponStats[j].m_iHitInBodygroups[k], + szHitgroupNames[k] ); + } + } + + filesystem->FPrintf( hFile, "\t\t\t</hit_in_hitgroups>\n" ); + + filesystem->FPrintf( hFile, "\t\t</weapon>\n" ); + } + } + + int numKilled = pPlayer->m_KilledPlayers.Count(); + for ( j=0;j<numKilled;j++ ) + { + filesystem->FPrintf( hFile, "<victim>\n" ); + filesystem->FPrintf( hFile, "\t<name>%s</name>\n", pPlayer->m_KilledPlayers[j].m_szPlayerName ); + filesystem->FPrintf( hFile, "\t<userid>%i</userid>\n", pPlayer->m_KilledPlayers[j].m_iUserID ); + filesystem->FPrintf( hFile, "\t<kills>%i</kills>\n", pPlayer->m_KilledPlayers[j].m_iKills ); + filesystem->FPrintf( hFile, "\t<damage>%i</damage>\n", pPlayer->m_KilledPlayers[j].m_iTotalDamage ); + filesystem->FPrintf( hFile, "</victim>" ); + } + + int numAttackers = pPlayer->m_KilledByPlayers.Count(); + for ( j=0;j<numAttackers;j++ ) + { + filesystem->FPrintf( hFile, "<attacker>" ); + filesystem->FPrintf( hFile, "\t<name>%s</name>\n", pPlayer->m_KilledByPlayers[j].m_szPlayerName ); + filesystem->FPrintf( hFile, "\t<userid>%i</userid>\n", pPlayer->m_KilledByPlayers[j].m_iUserID ); + filesystem->FPrintf( hFile, "\t<kills>%i</kills>\n", pPlayer->m_KilledByPlayers[j].m_iKills ); + filesystem->FPrintf( hFile, "\t<damage>%i</damage>\n", pPlayer->m_KilledByPlayers[j].m_iTotalDamage ); + filesystem->FPrintf( hFile, "</attacker>" ); + } + + filesystem->FPrintf( hFile, "\t</player>\n" ); + } + } + + // close stats + filesystem->FPrintf( hFile, "</stats>\n" ); + + filesystem->Close( hFile ); +} + +#include "dod_basegrenade.h" + +//========================================================== +// Called on physics entities that the player +uses ( if sv_turbophysics is on ) +// Here we want to exclude grenades +//========================================================== +bool CDODGameRules::CanEntityBeUsePushed( CBaseEntity *pEnt ) +{ + CDODBaseGrenade *pGrenade = dynamic_cast<CDODBaseGrenade *>( pEnt ); + + if ( pGrenade ) + { + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Engine asks for the list of convars that should tag the server +//----------------------------------------------------------------------------- +void CDODGameRules::GetTaggedConVarList( KeyValues *pCvarTagList ) +{ + BaseClass::GetTaggedConVarList( pCvarTagList ); + + KeyValues *pKV = new KeyValues( "tag" ); + pKV->SetString( "convar", "mp_fadetoblack" ); + pKV->SetString( "tag", "fadetoblack" ); + + pCvarTagList->AddSubKey( pKV ); +} + +#endif //indef CLIENT_DLL + +#ifdef CLIENT_DLL + +void CDODGameRules::SetRoundState( int iRoundState ) +{ + m_iRoundState = iRoundState; + + m_flLastRoundStateChangeTime = gpGlobals->curtime; +} + +#endif // CLIENT_DLL + +bool CDODGameRules::IsBombingTeam( int team ) +{ + if ( team == TEAM_ALLIES ) + return m_bAlliesAreBombing; + + if ( team == TEAM_AXIS ) + return m_bAxisAreBombing; + + return false; +} + +bool CDODGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ) +{ +#ifdef GAME_DLL + if( pPlayer ) + { + int iPlayerTeam = pPlayer->GetTeamNumber(); + if( ( iPlayerTeam == TEAM_ALLIES ) || ( iPlayerTeam == TEAM_AXIS ) ) + return false; + } +#else + int iLocalPlayerTeam = GetLocalPlayerTeam(); + if( ( iLocalPlayerTeam == TEAM_ALLIES ) || ( iLocalPlayerTeam == TEAM_AXIS ) ) + return false; +#endif + + return true; +} + + +#ifndef CLIENT_DLL + +ConVar dod_winter_never_drop_presents( "dod_winter_never_drop_presents", "0", FCVAR_CHEAT ); +ConVar dod_winter_always_drop_presents( "dod_winter_always_drop_presents", "0", FCVAR_CHEAT ); +ConVar dod_winter_present_drop_chance( "dod_winter_present_drop_chance", "0.2", FCVAR_CHEAT, "", true, 0.0, true, 1.0 ); + +float CDODGameRules::GetPresentDropChance( void ) +{ + if ( dod_winter_never_drop_presents.GetBool() ) + { + return 0.0; + } + + if ( dod_winter_always_drop_presents.GetBool() ) + { + return 1.0; + } + + if ( m_bWinterHolidayActive ) + { + return dod_winter_present_drop_chance.GetFloat(); + } + + return 0.0; +} + +#endif + +//========================= + +#ifndef CLIENT_DLL + +class CFuncTeamWall : public CBaseEntity +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CFuncTeamWall, CBaseEntity ); + +public: + virtual void Spawn(); + virtual bool KeyValue( const char *szKeyName, const char *szValue ) ; + virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; + + void WallTouch( CBaseEntity *pOther ); + + void DrawThink( void ); + +private: + Vector m_vecMaxs; + Vector m_vecMins; + + int m_iBlockTeam; + + float m_flNextHintTime; + + bool m_bShowWarning; +}; + +BEGIN_DATADESC( CFuncTeamWall ) + + DEFINE_KEYFIELD( m_iBlockTeam, FIELD_INTEGER, "blockteam" ), + + DEFINE_KEYFIELD( m_vecMaxs, FIELD_VECTOR, "maxs" ), + DEFINE_KEYFIELD( m_vecMins, FIELD_VECTOR, "mins" ), + + DEFINE_KEYFIELD( m_bShowWarning, FIELD_BOOLEAN, "warn" ), + + DEFINE_THINKFUNC( DrawThink ), + + DEFINE_FUNCTION( WallTouch ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_team_wall, CFuncTeamWall ); + +ConCommand cc_Load_Blocker_Walls( "load_enttext", Load_EntText, 0, FCVAR_CHEAT ); + +ConVar showblockerwalls( "showblockerwalls", "0", FCVAR_CHEAT, "Set to 1 to visualize blocker walls" ); + +void CFuncTeamWall::Spawn( void ) +{ + SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything + SetModel( STRING( GetModelName() ) ); + AddEffects( EF_NODRAW ); + SetSolid( SOLID_BBOX ); + + // set our custom collision if we declared this ent through the .ent file + if ( m_vecMins != vec3_origin && m_vecMaxs != vec3_origin ) + { + SetCollisionBounds( m_vecMins, m_vecMaxs ); + + // If we delcared an angle in the .ent file, make us OBB + if ( GetAbsAngles() != vec3_angle ) + { + SetSolid( SOLID_OBB ); + } + } + + SetThink( &CFuncTeamWall::DrawThink ); + SetNextThink( gpGlobals->curtime + 0.1 ); + + SetTouch( &CFuncTeamWall::WallTouch ); + m_flNextHintTime = gpGlobals->curtime; +} + +//----------------------------------------------------------------------------- +// Parse data from a map file +//----------------------------------------------------------------------------- +bool CFuncTeamWall::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "mins" )) + { + UTIL_StringToVector( m_vecMins.Base(), szValue ); + return true; + } + + if ( FStrEq( szKeyName, "maxs" )) + { + UTIL_StringToVector( m_vecMaxs.Base(), szValue ); + return true; + } + + if ( FStrEq( szKeyName, "warn" )) + { + m_bShowWarning = atoi(szValue) > 0; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +bool CFuncTeamWall::ShouldCollide( int collisionGroup, int contentsMask ) const +{ + bool bShouldCollide = false; + + if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) + { + switch ( m_iBlockTeam ) + { + case TEAM_UNASSIGNED: + bShouldCollide = ( contentsMask & ( CONTENTS_TEAM1 | CONTENTS_TEAM2 ) ) > 0; + break; + + case TEAM_ALLIES: + bShouldCollide = ( contentsMask & CONTENTS_TEAM1 ) > 0; + break; + + case TEAM_AXIS: + bShouldCollide = ( contentsMask & CONTENTS_TEAM2 ) > 0; + break; + + default: + break; + } + } + + return bShouldCollide; +} + +void CFuncTeamWall::WallTouch( CBaseEntity *pOther ) +{ + if ( !m_bShowWarning ) + return; + + if ( pOther && pOther->IsPlayer() ) + { + CDODPlayer *pPlayer = ToDODPlayer( pOther ); + + if ( pPlayer->GetTeamNumber() == m_iBlockTeam ) + { + // show a "go away" icon + if ( m_flNextHintTime < gpGlobals->curtime ) + { + pPlayer->HintMessage( "#dod_wrong_way" ); + + // global timer, but not critical to keep timer per player. + m_flNextHintTime = gpGlobals->curtime + 1.0; + } + } + } +} + +void CFuncTeamWall::DrawThink( void ) +{ + if ( showblockerwalls.GetBool() ) + { + NDebugOverlay::EntityBounds( this, 255, 0, 0, 0, 0.2 ); + } + + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +#endif + + +#ifdef GAME_DLL + + #include "modelentities.h" + + #define SF_TEAM_WALL_NO_HINT (1<<1) + + //----------------------------------------------------------------------------- + // Purpose: Visualizes a respawn room to the enemy team + //----------------------------------------------------------------------------- + class CFuncNewTeamWall : public CFuncBrush + { + DECLARE_CLASS( CFuncNewTeamWall, CFuncBrush ); + public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + + virtual int UpdateTransmitState( void ); + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; + + void WallTouch( CBaseEntity *pOther ); + + void SetActive( bool bActive ); + + private: + float m_flNextHintTime; + }; + + //=========================================================================================================== + + LINK_ENTITY_TO_CLASS( func_teamblocker, CFuncNewTeamWall ); + + BEGIN_DATADESC( CFuncNewTeamWall ) + END_DATADESC() + + IMPLEMENT_SERVERCLASS_ST( CFuncNewTeamWall, DT_FuncNewTeamWall ) + END_SEND_TABLE() + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void CFuncNewTeamWall::Spawn( void ) + { + BaseClass::Spawn(); + + SetActive( true ); + + SetCollisionGroup( DOD_COLLISIONGROUP_BLOCKERWALL ); + + if ( FBitSet( m_spawnflags, SF_TEAM_WALL_NO_HINT ) == false ) + { + SetTouch( &CFuncNewTeamWall::WallTouch ); + m_flNextHintTime = gpGlobals->curtime; + } + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + int CFuncNewTeamWall::UpdateTransmitState() + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } + + //----------------------------------------------------------------------------- + // Purpose: Only transmit this entity to clients that aren't in our team + //----------------------------------------------------------------------------- + int CFuncNewTeamWall::ShouldTransmit( const CCheckTransmitInfo *pInfo ) + { + return FL_EDICT_ALWAYS; + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void CFuncNewTeamWall::SetActive( bool bActive ) + { + if ( bActive ) + { + // We're a trigger, but we want to be solid. Out ShouldCollide() will make + // us non-solid to members of the team that spawns here. + RemoveSolidFlags( FSOLID_TRIGGER ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + } + else + { + AddSolidFlags( FSOLID_NOT_SOLID ); + AddSolidFlags( FSOLID_TRIGGER ); + } + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + bool CFuncNewTeamWall::ShouldCollide( int collisionGroup, int contentsMask ) const + { + if ( GetTeamNumber() == TEAM_UNASSIGNED ) + return false; + + if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) + { + switch( GetTeamNumber() ) + { + case TEAM_ALLIES: + if ( !(contentsMask & CONTENTS_TEAM1) ) + return false; + break; + + case TEAM_AXIS: + if ( !(contentsMask & CONTENTS_TEAM2) ) + return false; + break; + } + + return true; + } + + return false; + } + + void CFuncNewTeamWall::WallTouch( CBaseEntity *pOther ) + { + //if ( !m_bShowWarning ) + // return; + + if ( pOther && pOther->IsPlayer() ) + { + CDODPlayer *pPlayer = ToDODPlayer( pOther ); + + if ( pPlayer->GetTeamNumber() == GetTeamNumber() ) + { + // show a "go away" icon + if ( m_flNextHintTime < gpGlobals->curtime ) + { + pPlayer->HintMessage( "#dod_wrong_way" ); + + // global timer, but not critical to keep timer per player. + m_flNextHintTime = gpGlobals->curtime + 1.0; + } + } + } + } + +#else + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + class C_FuncNewTeamWall : public C_BaseEntity + { + DECLARE_CLASS( C_FuncNewTeamWall, C_BaseEntity ); + public: + DECLARE_CLIENTCLASS(); + + virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; + + virtual int DrawModel( int flags ); + }; + + IMPLEMENT_CLIENTCLASS_DT( C_FuncNewTeamWall, DT_FuncNewTeamWall, CFuncNewTeamWall ) + END_RECV_TABLE() + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + bool C_FuncNewTeamWall::ShouldCollide( int collisionGroup, int contentsMask ) const + { + if ( GetTeamNumber() == TEAM_UNASSIGNED ) + return false; + + if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) + { + switch( GetTeamNumber() ) + { + case TEAM_ALLIES: + if ( !(contentsMask & CONTENTS_TEAM1) ) + return false; + break; + + case TEAM_AXIS: + if ( !(contentsMask & CONTENTS_TEAM2) ) + return false; + break; + } + + return true; + } + + return false; + } + + int C_FuncNewTeamWall::DrawModel( int flags ) + { + return 1; + } + +#endif |