//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: The TF Game rules // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "cs_gamerules.h" #include "cs_ammodef.h" #include "weapon_csbase.h" #include "cs_shareddefs.h" #include "KeyValues.h" #include "cs_achievement_constants.h" #include "fmtstr.h" #ifdef CLIENT_DLL #include "networkstringtable_clientdll.h" #include "utlvector.h" #else #include "bot.h" #include "utldict.h" #include "cs_player.h" #include "cs_team.h" #include "cs_gamerules.h" #include "voice_gamemgr.h" #include "igamesystem.h" #include "weapon_c4.h" #include "mapinfo.h" #include "shake.h" #include "mapentities.h" #include "game.h" #include "cs_simple_hostage.h" #include "cs_gameinterface.h" #include "player_resource.h" #include "info_view_parameters.h" #include "cs_bot_manager.h" #include "cs_bot.h" #include "eventqueue.h" #include "fmtstr.h" #include "teamplayroundbased_gamerules.h" #include "gameweaponmanager.h" #include "cs_gamestats.h" #include "cs_urlretrieveprices.h" #include "networkstringtable_gamedll.h" #include "player_resource.h" #include "cs_player_resource.h" #if defined( REPLAY_ENABLED ) #include "replay/ireplaysystem.h" #include "replay/iserverreplaycontext.h" #include "replay/ireplaysessionrecorder.h" #endif // REPLAY_ENABLED #endif #include "cs_blackmarket.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifndef CLIENT_DLL #define CS_GAME_STATS_UPDATE 79200 //22 hours #define CS_GAME_STATS_UPDATE_PERIOD 7200 // 2 hours extern IUploadGameStats *gamestatsuploader; #if defined( REPLAY_ENABLED ) extern IReplaySystem *g_pReplay; #endif // REPLAY_ENABLED #endif /** * Player hull & eye position for standing, ducking, etc. This version has a taller * player height, but goldsrc-compatible collision bounds. */ static CViewVectors g_CSViewVectors( Vector( 0, 0, 64 ), // eye position Vector(-16, -16, 0 ), // hull min Vector( 16, 16, 62 ), // hull max Vector(-16, -16, 0 ), // duck hull min Vector( 16, 16, 45 ), // duck hull max Vector( 0, 0, 47 ), // duck view Vector(-10, -10, -10 ), // observer hull min Vector( 10, 10, 10 ), // observer hull max Vector( 0, 0, 14 ) // dead view height ); #ifndef CLIENT_DLL LINK_ENTITY_TO_CLASS(info_player_terrorist, CPointEntity); LINK_ENTITY_TO_CLASS(info_player_counterterrorist,CPointEntity); LINK_ENTITY_TO_CLASS(info_player_logo,CPointEntity); #endif REGISTER_GAMERULES_CLASS( CCSGameRules ); BEGIN_NETWORK_TABLE_NOBASE( CCSGameRules, DT_CSGameRules ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bFreezePeriod ) ), RecvPropInt( RECVINFO( m_iRoundTime ) ), RecvPropFloat( RECVINFO( m_fRoundStartTime ) ), RecvPropFloat( RECVINFO( m_flGameStartTime ) ), RecvPropInt( RECVINFO( m_iHostagesRemaining ) ), RecvPropBool( RECVINFO( m_bMapHasBombTarget ) ), RecvPropBool( RECVINFO( m_bMapHasRescueZone ) ), RecvPropBool( RECVINFO( m_bLogoMap ) ), RecvPropBool( RECVINFO( m_bBlackMarket ) ) #else SendPropBool( SENDINFO( m_bFreezePeriod ) ), SendPropInt( SENDINFO( m_iRoundTime ), 16 ), SendPropFloat( SENDINFO( m_fRoundStartTime ), 32, SPROP_NOSCALE ), SendPropFloat( SENDINFO( m_flGameStartTime ), 32, SPROP_NOSCALE ), SendPropInt( SENDINFO( m_iHostagesRemaining ), 4 ), SendPropBool( SENDINFO( m_bMapHasBombTarget ) ), SendPropBool( SENDINFO( m_bMapHasRescueZone ) ), SendPropBool( SENDINFO( m_bLogoMap ) ), SendPropBool( SENDINFO( m_bBlackMarket ) ) #endif END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( cs_gamerules, CCSGameRulesProxy ); IMPLEMENT_NETWORKCLASS_ALIASED( CSGameRulesProxy, DT_CSGameRulesProxy ) #ifdef CLIENT_DLL void RecvProxy_CSGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) { CCSGameRules *pRules = CSGameRules(); Assert( pRules ); *pOut = pRules; } BEGIN_RECV_TABLE( CCSGameRulesProxy, DT_CSGameRulesProxy ) RecvPropDataTable( "cs_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_CSGameRules ), RecvProxy_CSGameRules ) END_RECV_TABLE() #else void* SendProxy_CSGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) { CCSGameRules *pRules = CSGameRules(); Assert( pRules ); return pRules; } BEGIN_SEND_TABLE( CCSGameRulesProxy, DT_CSGameRulesProxy ) SendPropDataTable( "cs_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_CSGameRules ), SendProxy_CSGameRules ) END_SEND_TABLE() #endif ConVar ammo_50AE_max( "ammo_50AE_max", "35", FCVAR_REPLICATED ); ConVar ammo_762mm_max( "ammo_762mm_max", "90", FCVAR_REPLICATED ); ConVar ammo_556mm_max( "ammo_556mm_max", "90", FCVAR_REPLICATED ); ConVar ammo_556mm_box_max( "ammo_556mm_box_max", "200", FCVAR_REPLICATED ); ConVar ammo_338mag_max( "ammo_338mag_max", "30", FCVAR_REPLICATED ); ConVar ammo_9mm_max( "ammo_9mm_max", "120", FCVAR_REPLICATED ); ConVar ammo_buckshot_max( "ammo_buckshot_max", "32", FCVAR_REPLICATED ); ConVar ammo_45acp_max( "ammo_45acp_max", "100", FCVAR_REPLICATED ); ConVar ammo_357sig_max( "ammo_357sig_max", "52", FCVAR_REPLICATED ); ConVar ammo_57mm_max( "ammo_57mm_max", "100", FCVAR_REPLICATED ); ConVar ammo_hegrenade_max( "ammo_hegrenade_max", "1", FCVAR_REPLICATED ); ConVar ammo_flashbang_max( "ammo_flashbang_max", "2", FCVAR_REPLICATED ); ConVar ammo_smokegrenade_max( "ammo_smokegrenade_max", "1", FCVAR_REPLICATED ); //ConVar mp_dynamicpricing( "mp_dynamicpricing", "0", FCVAR_REPLICATED, "Enables or Disables the dynamic weapon prices" ); extern ConVar sv_stopspeed; ConVar mp_buytime( "mp_buytime", "1.5", FCVAR_REPLICATED, "How many minutes after round start players can buy items for.", true, 0.25, false, 0 ); ConVar mp_playerid( "mp_playerid", "0", FCVAR_REPLICATED, "Controls what information player see in the status bar: 0 all names; 1 team names; 2 no names", true, 0, true, 2 ); ConVar mp_playerid_delay( "mp_playerid_delay", "0.5", FCVAR_REPLICATED, "Number of seconds to delay showing information in the status bar", true, 0, true, 1 ); ConVar mp_playerid_hold( "mp_playerid_hold", "0.25", FCVAR_REPLICATED, "Number of seconds to keep showing old information in the status bar", true, 0, true, 1 ); ConVar mp_round_restart_delay( "mp_round_restart_delay", "5.0", FCVAR_REPLICATED, "Number of seconds to delay before restarting a round after a win", true, 0.0f, true, 10.0f ); ConVar sv_allowminmodels( "sv_allowminmodels", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Allow or disallow the use of cl_minmodels on this server." ); #ifdef CLIENT_DLL ConVar cl_autowepswitch( "cl_autowepswitch", "1", FCVAR_ARCHIVE | FCVAR_USERINFO, "Automatically switch to picked up weapons (if more powerful)" ); ConVar cl_autohelp( "cl_autohelp", "1", FCVAR_ARCHIVE | FCVAR_USERINFO, "Auto-help" ); #else // longest the intermission can last, in seconds #define MAX_INTERMISSION_TIME 120 // Falling damage stuff. #define CS_PLAYER_FATAL_FALL_SPEED 1100 // approx 60 feet #define CS_PLAYER_MAX_SAFE_FALL_SPEED 580 // approx 20 feet #define CS_DAMAGE_FOR_FALL_SPEED ((float)100 / ( CS_PLAYER_FATAL_FALL_SPEED - CS_PLAYER_MAX_SAFE_FALL_SPEED )) // damage per unit per second. // These entities are preserved each round restart. The rest are removed and recreated. static const char *s_PreserveEnts[] = { "ai_network", "ai_hint", "cs_gamerules", "cs_team_manager", "cs_player_manager", "env_soundscape", "env_soundscape_proxy", "env_soundscape_triggerable", "env_sun", "env_wind", "env_fog_controller", "func_brush", "func_wall", "func_buyzone", "func_illusionary", "func_hostage_rescue", "func_bomb_target", "infodecal", "info_projecteddecal", "info_node", "info_target", "info_node_hint", "info_player_counterterrorist", "info_player_terrorist", "info_map_parameters", "keyframe_rope", "move_rope", "info_ladder", "player", "point_viewcontrol", "scene_manager", "shadow_control", "sky_camera", "soundent", "trigger_soundscape", "viewmodel", "predicted_viewmodel", "worldspawn", "point_devshot_camera", "", // END Marker }; // --------------------------------------------------------------------------------------------------- // // 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_CT, TEAM_SPECTATOR, etc. const char *sTeamNames[] = { "Unassigned", "Spectator", "TERRORIST", "CT" }; extern ConVar mp_maxrounds; ConVar mp_startmoney( "mp_startmoney", "800", FCVAR_REPLICATED | FCVAR_NOTIFY, "amount of money each player gets when they reset", true, 800, true, 16000 ); ConVar mp_roundtime( "mp_roundtime", "2.5", FCVAR_REPLICATED | FCVAR_NOTIFY, "How many minutes each round takes.", true, 1, // min value true, 9 // max value ); ConVar mp_freezetime( "mp_freezetime", "6", FCVAR_REPLICATED | FCVAR_NOTIFY, "how many seconds to keep players frozen when the round starts", true, 0, // min value true, 60 // max value ); ConVar mp_c4timer( "mp_c4timer", "45", FCVAR_REPLICATED | FCVAR_NOTIFY, "how long from when the C4 is armed until it blows", true, 10, // min value true, 90 // max value ); ConVar mp_limitteams( "mp_limitteams", "2", FCVAR_REPLICATED | FCVAR_NOTIFY, "Max # of players 1 team can have over another (0 disables check)", true, 0, // min value true, 30 // max value ); ConVar mp_tkpunish( "mp_tkpunish", "0", FCVAR_REPLICATED, "Will a TK'er be punished in the next round? {0=no, 1=yes}" ); ConVar mp_autokick( "mp_autokick", "1", FCVAR_REPLICATED, "Kick idle/team-killing players" ); ConVar mp_spawnprotectiontime( "mp_spawnprotectiontime", "5", FCVAR_REPLICATED, "Kick players who team-kill within this many seconds of a round restart." ); ConVar mp_humanteam( "mp_humanteam", "any", FCVAR_REPLICATED, "Restricts human players to a single team {any, CT, T}" ); ConVar mp_ignore_round_win_conditions( "mp_ignore_round_win_conditions", "0", FCVAR_REPLICATED, "Ignore conditions which would end the current round"); ConCommand EndRound( "endround", &CCSGameRules::EndRound, "End the current round.", FCVAR_CHEAT ); // --------------------------------------------------------------------------------------------------- // // Global helper functions. // --------------------------------------------------------------------------------------------------- // void InitBodyQue(void) { // FIXME: Make this work } 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; } //----------------------------------------------------------------------------- // 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. float flPadSize = 5; Vector vTestMins = mins - Vector( flPadSize, flPadSize, flPadSize ); Vector vTestMaxs = maxs + Vector( flPadSize, flPadSize, flPadSize ); // 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 < 3; 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; } int UTIL_HumansInGame( bool ignoreSpectators ) { int iCount = 0; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *entity = CCSPlayer::Instance( i ); if ( entity && !FNullEnt( entity->edict() ) ) { if ( FStrEq( entity->GetPlayerName(), "" ) ) continue; if ( FBitSet( entity->GetFlags(), FL_FAKECLIENT ) ) continue; if ( ignoreSpectators && entity->GetTeamNumber() != TEAM_TERRORIST && entity->GetTeamNumber() != TEAM_CT ) continue; if ( ignoreSpectators && entity->State_Get() == STATE_PICKINGCLASS ) continue; iCount++; } } return iCount; } // --------------------------------------------------------------------------------------------------- // // CCSGameRules implementation. // --------------------------------------------------------------------------------------------------- // CCSGameRules::CCSGameRules() { m_iRoundTime = 0; m_iRoundWinStatus = WINNER_NONE; m_iFreezeTime = 0; m_fRoundStartTime = 0; m_bAllowWeaponSwitch = true; m_bFreezePeriod = true; m_iNumTerrorist = m_iNumCT = 0; // number of players per team m_flRestartRoundTime = 0.1f; // restart first round as soon as possible m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0; m_bFirstConnected = false; m_bCompleteReset = false; m_iAccountTerrorist = m_iAccountCT = 0; m_iNumCTWins = 0; m_iNumTerroristWins = 0; m_iNumConsecutiveCTLoses = 0; m_iNumConsecutiveTerroristLoses = 0; m_bTargetBombed = false; m_bBombDefused = false; m_iTotalRoundsPlayed = -1; m_iUnBalancedRounds = 0; m_flGameStartTime = 0; m_iHostagesRemaining = 0; m_bLevelInitialized = false; m_bLogoMap = false; m_tmNextPeriodicThink = 0; m_bMapHasBombTarget = false; m_bMapHasRescueZone = false; m_iSpawnPointCount_Terrorist = 0; m_iSpawnPointCount_CT = 0; m_bTCantBuy = false; m_bCTCantBuy = false; m_bMapHasBuyZone = false; m_iLoserBonus = 0; m_iHostagesRescued = 0; m_iHostagesTouched = 0; m_flNextHostageAnnouncement = 0.0f; //============================================================================= // HPE_BEGIN // [dwenger] Reset rescue-related achievement values //============================================================================= // [tj] reset flawless and lossless round related flags m_bNoTerroristsKilled = true; m_bNoCTsKilled = true; m_bNoTerroristsDamaged = true; m_bNoCTsDamaged = true; m_pFirstKill = NULL; m_firstKillTime = 0; // [menglish] Reset fun fact values m_pFirstBlood = NULL; m_firstBloodTime = 0; m_bCanDonateWeapons = true; // [dwenger] Reset rescue-related achievement values m_pLastRescuer = NULL; m_iNumRescuers = 0; m_hostageWasInjured = false; m_hostageWasKilled = false; m_pFunFactManager = new CCSFunFactMgr(); m_pFunFactManager->Init(); //============================================================================= // HPE_END //============================================================================= m_iHaveEscaped = 0; m_bMapHasEscapeZone = false; m_iNumEscapers = 0; m_iNumEscapeRounds = 0; m_iMapHasVIPSafetyZone = 0; m_pVIP = NULL; m_iConsecutiveVIP = 0; m_bMapHasBombZone = false; m_bBombDropped = false; m_bBombPlanted = false; m_pLastBombGuy = NULL; m_bAllowWeaponSwitch = true; m_flNextHostageAnnouncement = gpGlobals->curtime; // asap. ReadMultiplayCvars(); m_pPrices = NULL; m_bBlackMarket = false; m_bDontUploadStats = false; // Create the team managers for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ ) { CTeam *pTeam = static_cast(CreateEntityByName( "cs_team_manager" )); pTeam->Init( sTeamNames[i], i ); g_Teams.AddToTail( pTeam ); } if ( filesystem->FileExists( UTIL_VarArgs( "maps/cfg/%s.cfg", STRING(gpGlobals->mapname) ) ) ) { // Execute a map specific cfg file - as in Day of Defeat // Map names cannot contain quotes or control characters so this is safe but silly that we have to do it. engine->ServerCommand( UTIL_VarArgs( "exec \"%s.cfg\" */maps\n", STRING(gpGlobals->mapname) ) ); engine->ServerExecute(); } #ifndef CLIENT_DLL // stats if ( g_flGameStatsUpdateTime == 0.0f ) { memset( g_iWeaponPurchases, 0, sizeof( g_iWeaponPurchases) ); memset( g_iTerroristVictories, 0, sizeof( g_iTerroristVictories) ); memset( g_iCounterTVictories, 0, sizeof( g_iTerroristVictories) ); g_flGameStatsUpdateTime = CS_GAME_STATS_UPDATE; //Next update is between 22 and 24 hours. } #endif } void CCSGameRules::AddPricesToTable( weeklyprice_t prices ) { int iIndex = m_StringTableBlackMarket->FindStringIndex( "blackmarket_prices" ); if ( iIndex == INVALID_STRING_INDEX ) { m_StringTableBlackMarket->AddString( CBaseEntity::IsServer(), "blackmarket_prices", sizeof( weeklyprice_t), &prices ); } else { m_StringTableBlackMarket->SetStringUserData( iIndex, sizeof( weeklyprice_t), &prices ); } SetBlackMarketPrices( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CCSGameRules::~CCSGameRules() { // Note, don't delete each team since they are in the gEntList and will // automatically be deleted from there, instead. g_Teams.Purge(); if( m_pFunFactManager ) { delete m_pFunFactManager; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCSGameRules::UpdateClientData( CBasePlayer *player ) { } //----------------------------------------------------------------------------- // Purpose: TF2 Specific Client Commands // Input : // Output : //----------------------------------------------------------------------------- bool CCSGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) { CCSPlayer *pPlayer = ToCSPlayer( pEdict ); if ( FStrEq( args[0], "changeteam" ) ) { return true; } else if ( FStrEq( args[0], "nextmap" ) ) { if ( pPlayer->m_iNextTimeCheck < 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); pPlayer->m_iNextTimeCheck = gpGlobals->curtime + 1; } return true; } else if( pPlayer->ClientCommand( args ) ) { return true; } else if( BaseClass::ClientCommand( pEdict, args ) ) { return true; } else if ( TheBots->ServerCommand( args.GetCommandString() ) ) { return true; } else { return TheBots->ClientCommand( pPlayer, args ); } } //----------------------------------------------------------------------------- // Purpose: Player has just spawned. Equip them. //----------------------------------------------------------------------------- void CCSGameRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) { CCSPlayer *pPlayer = dynamic_cast< CCSPlayer * >( CBaseEntity::Instance( pEntity ) ); if ( pPlayer ) { char const *pszCommand = pKeyValues->GetName(); if ( pszCommand && pszCommand[0] ) { if ( FStrEq( pszCommand, "ClanTagChanged" ) ) { pPlayer->SetClanTag( pKeyValues->GetString( "tag", "" ) ); const char *teamName = "UNKNOWN"; if ( pPlayer->GetTeam() ) { teamName = pPlayer->GetTeam()->GetName(); } UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"clantag\" (value \"%s\")\n", pPlayer->GetPlayerName(), pPlayer->GetUserID(), pPlayer->GetNetworkIDString(), teamName, pKeyValues->GetString( "tag", "unknown" ) ); } } } BaseClass::ClientCommandKeyValues( pEntity, pKeyValues ); } //----------------------------------------------------------------------------- // Purpose: Player has just spawned. Equip them. //----------------------------------------------------------------------------- void CCSGameRules::PlayerSpawn( CBasePlayer *pBasePlayer ) { CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer ); if ( !pPlayer ) Error( "PlayerSpawn" ); if ( pPlayer->State_Get() != STATE_ACTIVE ) return; pPlayer->EquipSuit(); bool addDefault = true; CBaseEntity *pWeaponEntity = NULL; while ( ( pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL ) { if ( addDefault ) { // remove all our weapons and armor before touching the first game_player_equip pPlayer->RemoveAllItems( true ); } pWeaponEntity->Touch( pPlayer ); addDefault = false; } if ( addDefault || pPlayer->m_bIsVIP ) pPlayer->GiveDefaultItems(); } void CCSGameRules::BroadcastSound( const char *sound, int team ) { CBroadcastRecipientFilter filter; filter.MakeReliable(); if( team != -1 ) { filter.RemoveAllRecipients(); filter.AddRecipientsByTeam( GetGlobalTeam(team) ); } UserMessageBegin ( filter, "SendAudio" ); WRITE_STRING( sound ); MessageEnd(); } //----------------------------------------------------------------------------- // 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 CCSGameRules::GetExplosionDamageAdjustment(Vector & vecSrc, Vector & vecEnd, CBaseEntity *pEntityToIgnore) { float retval = 0.0; trace_t tr; UTIL_TraceLine(vecSrc, vecEnd, MASK_SHOT, pEntityToIgnore, COLLISION_GROUP_NONE, &tr); if (tr.fraction == 1.0) { retval = 1.0; } else if (!(tr.DidHitWorld()) && (tr.m_pEnt != NULL) && (tr.m_pEnt != pEntityToIgnore) && (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 DENSITY_ABSORB_ALL_DAMAGE = 3000.0; float scale = flDensity / 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 CCSGameRules::GetAmountOfEntityVisible(Vector & vecSrc, CBaseEntity *entity) { float retval = 0.0; const float damagePercentageChest = 0.40; const float damagePercentageHead = 0.20; const float damagePercentageFeet = 0.20; const float damagePercentageRightSide = 0.10; const float damagePercentageLeftSide = 0.10; 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); } CCSPlayer *player = (CCSPlayer *)entity; // check what parts of the player we can see from this point and modify the return value accordingly. float chestHeightFromFeet; float armDistanceFromChest = HalfHumanWidth; // calculate positions of various points on the target player's body Vector vecFeet = player->GetAbsOrigin(); Vector vecChest = player->BodyTarget(vecSrc, false); chestHeightFromFeet = vecChest.z - vecFeet.z; // compute the distance from the chest to the feet. (this accounts for ducking and the like) Vector vecHead = player->GetAbsOrigin(); vecHead.z += HumanHeight; Vector vecRightFacing; AngleVectors(player->GetAbsAngles(), NULL, &vecRightFacing, NULL); vecRightFacing.NormalizeInPlace(); vecRightFacing = vecRightFacing * armDistanceFromChest; Vector vecLeftSide = player->GetAbsOrigin(); vecLeftSide.x -= vecRightFacing.x; vecLeftSide.y -= vecRightFacing.y; vecLeftSide.z += chestHeightFromFeet; Vector vecRightSide = player->GetAbsOrigin(); vecRightSide.x += vecRightFacing.x; vecRightSide.y += vecRightFacing.y; vecRightSide.z += chestHeightFromFeet; // check chest float damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecChest, entity); retval += (damagePercentageChest * damageAdjustment); // check top of head damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecHead, entity); retval += (damagePercentageHead * damageAdjustment); // check feet damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecFeet, entity); retval += (damagePercentageFeet * damageAdjustment); // check left "edge" damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecLeftSide, entity); retval += (damagePercentageLeftSide * damageAdjustment); // check right "edge" damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRightSide, entity); retval += (damagePercentageRightSide * damageAdjustment); return retval; } void CCSGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity * pEntityIgnore ) { RadiusDamage( info, vecSrcIn, flRadius, iClassIgnore, false ); } // Add the ability to ignore the world trace void CCSGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, bool bIgnoreWorld ) { CBaseEntity *pEntity = NULL; trace_t tr; float falloff, damagePercentage; Vector vecSpot; Vector vecToTarget; Vector vecEndPos; //============================================================================= // HPE_BEGIN: //============================================================================= // [tj] The number of enemy players this explosion killed int numberOfEnemyPlayersKilledByThisExplosion = 0; // [tj] who we award the achievement to if enough players are killed CCSPlayer* pCSExplosionAttacker = ToCSPlayer(info.GetAttacker()); // [tj] used to determine which achievement to award for sufficient kills CBaseEntity* pInflictor = info.GetInflictor(); bool isGrenade = pInflictor && V_strcmp(pInflictor->GetClassname(), "hegrenade_projectile") == 0; bool isBomb = pInflictor && V_strcmp(pInflictor->GetClassname(), "planted_c4") == 0; //============================================================================= // HPE_END //============================================================================= vecEndPos.Init(); Vector vecSrc = vecSrcIn; damagePercentage = 1.0; if ( flRadius ) falloff = info.GetDamage() / flRadius; else falloff = 1.0; int bInWater = (UTIL_PointContents ( vecSrc ) & MASK_WATER) ? true : false; vecSrc.z += 1;// in case grenade is lying on the ground // iterate on all entities in the vicinity. for ( CEntitySphereQuery sphere( vecSrc, flRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) { //============================================================================= // HPE_BEGIN: // [tj] We have to save whether or not the player is killed so we don't give credit // for pre-dead players. //============================================================================= bool wasAliveBeforeExplosion = false; CCSPlayer* pCSExplosionVictim = ToCSPlayer(pEntity); if (pCSExplosionVictim) { wasAliveBeforeExplosion = pCSExplosionVictim->IsAlive(); } //============================================================================= // HPE_END //============================================================================= if ( pEntity->m_takedamage != DAMAGE_NO ) { // UNDONE: this should check a damage mask, not an ignore if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) {// houndeyes don't hurt other houndeyes with their attack continue; } // blasts don't travel into or out of water if ( !bIgnoreWorld ) { if (bInWater && pEntity->GetWaterLevel() == 0) continue; if (!bInWater && pEntity->GetWaterLevel() == 3) continue; } // radius damage can only be blocked by the world vecSpot = pEntity->BodyTarget( vecSrc ); bool bHit = false; if( bIgnoreWorld ) { vecEndPos = vecSpot; bHit = true; } else { // get the percentage of the target entity that is visible from the // explosion position. damagePercentage = GetAmountOfEntityVisible(vecSrc, pEntity); if (damagePercentage > 0.0) { vecEndPos = vecSpot; bHit = true; } } if ( bHit ) { // the explosion can 'see' this entity, so hurt them! //vecToTarget = ( vecSrc - vecEndPos ); vecToTarget = ( vecEndPos - vecSrc ); // use a Gaussian function to describe the damage falloff over distance, with flRadius equal to 3 * sigma // this results in the following values: // // Range Fraction Damage // 0.0 100% // 0.1 96% // 0.2 84% // 0.3 67% // 0.4 49% // 0.5 32% // 0.6 20% // 0.7 11% // 0.8 6% // 0.9 3% // 1.0 1% float fDist = vecToTarget.Length(); float fSigma = flRadius / 3.0f; // flRadius specifies 3rd standard deviation (0.0111 damage at this range) float fGaussianFalloff = exp(-fDist * fDist / (2.0f * fSigma * fSigma)); float flAdjustedDamage = info.GetDamage() * fGaussianFalloff * damagePercentage; 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 ); } Vector vecTarget; vecTarget = pEntity->BodyTarget(vecSrc, false); UTIL_TraceLine(vecSrc, vecTarget, MASK_SHOT, NULL, COLLISION_GROUP_NONE, &tr); // blasts always hit chest tr.hitgroup = HITGROUP_GENERIC; if (tr.fraction != 1.0) { // this has to be done to make breakable glass work. ClearMultiDamage( ); pEntity->DispatchTraceAttack( adjustedInfo, dir, &tr ); ApplyMultiDamage(); } else { pEntity->TakeDamage( adjustedInfo ); } // Now hit all triggers along the way that respond to damage... pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, vecEndPos, dir ); //============================================================================= // HPE_BEGIN: // [sbodenbender] Increment grenade damage stat //============================================================================= if (pCSExplosionVictim && pCSExplosionAttacker && isGrenade) { CCS_GameStats.IncrementStat(pCSExplosionAttacker, CSSTAT_GRENADE_DAMAGE, static_cast(adjustedInfo.GetDamage())); } //============================================================================= // HPE_END //============================================================================= } } } //============================================================================= // HPE_BEGIN: // [tj] Count up victims of area of effect damage for achievement purposes //============================================================================= if (pCSExplosionVictim) { //If the bomb is exploding, set the attacker to the planter (we can't put this in the CTakeDamageInfo, since //players aren't supposed to get credit for bomb kills) if (isBomb) { CPlantedC4* bomb = static_cast (pInflictor); if (bomb) { pCSExplosionAttacker = bomb->GetPlanter(); } } //Count check to make sure we killed an enemy player if( pCSExplosionAttacker && !pCSExplosionVictim->IsAlive() && wasAliveBeforeExplosion && pCSExplosionVictim->GetTeamNumber() != pCSExplosionAttacker->GetTeamNumber()) { numberOfEnemyPlayersKilledByThisExplosion++; } } //============================================================================= // HPE_END //============================================================================= } //============================================================================= // HPE_BEGIN: // [tj] //Depending on which type of explosion it was, award the appropriate achievement. //============================================================================= if (pCSExplosionAttacker && isGrenade && numberOfEnemyPlayersKilledByThisExplosion >= AchievementConsts::GrenadeMultiKill_MinKills) { pCSExplosionAttacker->AwardAchievement(CSGrenadeMultikill); pCSExplosionAttacker->CheckMaxGrenadeKills(numberOfEnemyPlayersKilledByThisExplosion); } if (pCSExplosionAttacker && isBomb && numberOfEnemyPlayersKilledByThisExplosion >= AchievementConsts::BombMultiKill_MinKills) { pCSExplosionAttacker->AwardAchievement(CSBombMultikill); } //============================================================================= // HPE_END //============================================================================= } //----------------------------------------------------------------------------- // Purpose: // Input : *pVictim - // *pKiller - // *pInflictor - //----------------------------------------------------------------------------- void CCSGameRules::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(); CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); CCSPlayer *pCSVictim = (CCSPlayer*)(pVictim); bool bHeadshot = false; if ( pScorer ) // Is the killer a client? { killer_ID = pScorer->GetUserID(); if( info.GetDamageType() & DMG_HEADSHOT ) { //to enable drawing the headshot icon as well as the weapon icon, bHeadshot = true; } if ( pInflictor ) { if ( pInflictor == pScorer ) { // If the inflictor is the killer, then it must be their current weapon doing the damage if ( pScorer->GetActiveWeapon() ) { killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); //GetDeathNoticeName(); } } 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, "NPC_", 8 ) == 0 ) { killer_weapon_name += 8; } else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) { killer_weapon_name += 5; } else if( strncmp( killer_weapon_name, "hegrenade", 9 ) == 0 ) //"hegrenade_projectile" { killer_weapon_name = "hegrenade"; } else if( strncmp( killer_weapon_name, "flashbang", 9 ) == 0 ) //"flashbang_projectile" { killer_weapon_name = "flashbang"; } 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("headshot", bHeadshot ? 1 : 0 ); event->SetInt("priority", bHeadshot ? 8 : 7 ); // HLTV event priority, not transmitted if ( pCSVictim->GetDeathFlags() & CS_DEATH_DOMINATION ) { event->SetInt( "dominated", 1 ); } else if ( pCSVictim->GetDeathFlags() & CS_DEATH_REVENGE ) { event->SetInt( "revenge", 1 ); } gameeventmanager->FireEvent( event ); } } //========================================================= //========================================================= void CCSGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); CCSPlayer *pCSVictim = (CCSPlayer *)pVictim; CCSPlayer *pCSScorer = (CCSPlayer *)pScorer; CCS_GameStats.PlayerKilled( pVictim, info ); //============================================================================= // HPE_BEGIN: // [tj] Flag the round as non-lossless for the appropriate team. // [menglish] Set the death flags depending on a nemesis system //============================================================================= if (pVictim->GetTeamNumber() == TEAM_TERRORIST) { m_bNoTerroristsKilled = false; m_bNoTerroristsDamaged = false; } if (pVictim->GetTeamNumber() == TEAM_CT) { m_bNoCTsKilled = false; m_bNoCTsDamaged = false; } m_bCanDonateWeapons = false; if ( m_pFirstKill == NULL && pCSScorer != pVictim ) { m_pFirstKill = pCSScorer; m_firstKillTime = gpGlobals->curtime - m_fRoundStartTime; } // determine if this kill affected a nemesis relationship int iDeathFlags = 0; if ( pScorer ) { CCS_GameStats.CalculateOverkill( pCSScorer, pCSVictim); CCS_GameStats.CalcDominationAndRevenge( pCSScorer, pCSVictim, &iDeathFlags ); } pCSVictim->SetDeathFlags( iDeathFlags ); //============================================================================= // HPE_END //============================================================================= // If we're killed by the C4, we do a subset of BaseClass::PlayerKilled() // Specifically, we shouldn't lose any points or show death notices, to match goldsrc if ( Q_strcmp(pKiller->GetClassname(), "planted_c4" ) == 0 ) { // 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 ); } else { BaseClass::PlayerKilled( pVictim, info ); } // check for team-killing, and give monetary rewards/penalties // Find the killer & the scorer if ( !pScorer ) return; if ( IPointsForKill( pScorer, pVictim ) < 0 ) { // team-killer! pCSScorer->AddAccount( -3300 ); ++pCSScorer->m_iTeamKills; pCSScorer->m_bJustKilledTeammate = true; ClientPrint( pCSScorer, HUD_PRINTCENTER, "#Killed_Teammate" ); if ( mp_autokick.GetBool() ) { char strTeamKills[64]; Q_snprintf( strTeamKills, sizeof( strTeamKills ), "%d", pCSScorer->m_iTeamKills ); ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Game_teammate_kills", strTeamKills ); // this includes a " of 3" in it if ( pCSScorer->m_iTeamKills >= 3 ) { ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates" ); engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pCSScorer->GetUserID() ) ); } else if ( mp_spawnprotectiontime.GetInt() > 0 && GetRoundElapsedTime() < mp_spawnprotectiontime.GetInt() ) { ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates" ); engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pCSScorer->GetUserID() ) ); } } if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_FRIEND_KILLED) ) { pCSScorer->m_iDisplayHistoryBits |= DHF_FRIEND_KILLED; pCSScorer->HintMessage( "#Hint_careful_around_teammates", false ); } } else { //============================================================================= // HPE_BEGIN: // [tj] Added a check to make sure we don't get money for suicides. //============================================================================= if (pCSScorer != pCSVictim) { //============================================================================= // HPE_END //============================================================================= if ( pCSVictim->IsVIP() ) { pCSScorer->HintMessage( "#Hint_reward_for_killing_vip", true ); pCSScorer->AddAccount( 2500 ); } else { pCSScorer->AddAccount( 300 ); } } if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_ENEMY_KILLED) ) { pCSScorer->m_iDisplayHistoryBits |= DHF_ENEMY_KILLED; pCSScorer->HintMessage( "#Hint_win_round_by_killing_enemy", false ); } } } void CCSGameRules::InitDefaultAIRelationships() { // Allocate memory for default relationships CBaseCombatCharacter::AllocateDefaultRelationships(); // -------------------------------------------------------------- // First initialize table so we can report missing relationships // -------------------------------------------------------------- int i, j; for (i=0;iIsBeingGivenItem() ) bIsBeingGivenItem = true; if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() && !bIsBeingGivenItem ) { // Player has an active item, so let's check cl_autowepswitch. const char *cl_autowepswitch = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_autowepswitch" ); if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 ) { return false; } } if ( pPlayer->IsBot() && !bIsBeingGivenItem ) { return false; } if ( !GetAllowWeaponSwitch() ) { return false; } return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon ); } //----------------------------------------------------------------------------- // Purpose: // Input : allow - //----------------------------------------------------------------------------- void CCSGameRules::SetAllowWeaponSwitch( bool allow ) { m_bAllowWeaponSwitch = allow; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CCSGameRules::GetAllowWeaponSwitch() { return m_bAllowWeaponSwitch; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPlayer - // Output : const char //----------------------------------------------------------------------------- const char *CCSGameRules::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { Assert( pPlayer ); return BaseClass::SetDefaultPlayerTeam( pPlayer ); } void CCSGameRules::LevelInitPreEntity() { BaseClass::LevelInitPreEntity(); // TODO for CZ-style hostages: TheHostageChatter->Precache(); } void CCSGameRules::LevelInitPostEntity() { BaseClass::LevelInitPostEntity(); m_bLevelInitialized = false; // re-count CT and T start spots now that they exist // Figure out from the entities in the map what kind of map this is (bomb run, prison escape, etc). CheckMapConditions(); } INetworkStringTable *g_StringTableBlackMarket = NULL; void CCSGameRules::CreateCustomNetworkStringTables( void ) { m_StringTableBlackMarket = g_StringTableBlackMarket; if ( 0 )//mp_dynamicpricing.GetBool() ) { m_bBlackMarket = BlackMarket_DownloadPrices(); if ( m_bBlackMarket == false ) { Msg( "ERROR: mp_dynamicpricing set to 1 but couldn't download the price list!\n" ); } } else { m_bBlackMarket = false; SetBlackMarketPrices( true ); } } float CCSGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) { float fFallVelocity = pPlayer->m_Local.m_flFallVelocity - CS_PLAYER_MAX_SAFE_FALL_SPEED; float fallDamage = fFallVelocity * CS_DAMAGE_FOR_FALL_SPEED * 1.25; if ( fallDamage > 0.0f ) { // let the bots know IGameEvent * event = gameeventmanager->CreateEvent( "player_falldamage" ); if ( event ) { event->SetInt( "userid", pPlayer->GetUserID() ); event->SetFloat( "damage", fallDamage ); event->SetInt( "priority", 4 ); // HLTV event priority, not transmitted gameeventmanager->FireEvent( event ); } } return fallDamage; } void CCSGameRules::ClientDisconnected( edict_t *pClient ) { BaseClass::ClientDisconnected( pClient ); //============================================================================= // HPE_BEGIN: // [tj] Clear domination data when a player disconnects //============================================================================= CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pClient ) ); if ( pPlayer ) { pPlayer->RemoveNemesisRelationships(); } //============================================================================= // HPE_END //============================================================================= CheckWinConditions(); } // Called when game rules are destroyed by CWorld void CCSGameRules::LevelShutdown() { int iLevelIndex = GetCSLevelIndex( STRING( gpGlobals->mapname ) ); if ( iLevelIndex != -1 ) { g_iTerroristVictories[iLevelIndex] += m_iNumTerroristWins; g_iCounterTVictories[iLevelIndex] += m_iNumCTWins; } BaseClass::LevelShutdown(); } //--------------------------------------------------------------------------------------------------- /** * Check if the scenario has been won/lost. * Return true if the scenario is over, false if the scenario is still in progress */ bool CCSGameRules::CheckWinConditions( void ) { if ( mp_ignore_round_win_conditions.GetBool() ) { return false; } // If a winner has already been determined.. then get the heck out of here if (m_iRoundWinStatus != WINNER_NONE) { // still check if we lost players to where we need to do a full reset next round... int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); bool bNeededPlayers = false; NeededPlayersCheck( bNeededPlayers ); return true; } // Initialize the player counts.. int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); /***************************** OTHER PLAYER's CHECK *********************************************************/ bool bNeededPlayers = false; if ( NeededPlayersCheck( bNeededPlayers ) ) return false; /****************************** ASSASINATION/VIP SCENARIO CHECK *******************************************************/ if ( VIPRoundEndCheck( bNeededPlayers ) ) return true; /****************************** PRISON ESCAPE CHECK *******************************************************/ if ( PrisonRoundEndCheck() ) return true; /****************************** BOMB CHECK ********************************************************/ if ( BombRoundEndCheck( bNeededPlayers ) ) return true; /***************************** TEAM EXTERMINATION CHECK!! *********************************************************/ // CounterTerrorists won by virture of elimination if ( TeamExterminationCheck( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT, bNeededPlayers ) ) return true; /******************************** HOSTAGE RESCUE CHECK ******************************************************/ if ( HostageRescueRoundEndCheck( bNeededPlayers ) ) return true; // scenario not won - still in progress return false; } bool CCSGameRules::NeededPlayersCheck( bool &bNeededPlayers ) { // We needed players to start scoring // Do we have them now? if( !m_iNumSpawnableTerrorist || !m_iNumSpawnableCT ) { Msg( "Game will not start until both teams have players.\n" ); UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_scoring" ); bNeededPlayers = true; m_bFirstConnected = false; } if ( !m_bFirstConnected && m_iNumSpawnableTerrorist && m_iNumSpawnableCT ) { // Start the round immediately when the first person joins // UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" ); m_bFreezePeriod = false; //Make sure we are not on the FreezePeriod. m_bCompleteReset = true; TerminateRound( 3.0f, Game_Commencing ); m_bFirstConnected = true; return true; } return false; } void CCSGameRules::InitializePlayerCounts( int &NumAliveTerrorist, int &NumAliveCT, int &NumDeadTerrorist, int &NumDeadCT ) { NumAliveTerrorist = NumAliveCT = NumDeadCT = NumDeadTerrorist = 0; m_iNumTerrorist = m_iNumCT = m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0; m_iHaveEscaped = 0; // Count how many dead players there are on each team. for ( int iTeam=0; iTeam < GetNumberOfTeams(); iTeam++ ) { CTeam *pTeam = GetGlobalTeam( iTeam ); for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) { CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); Assert( pPlayer ); if ( !pPlayer ) continue; Assert( pPlayer->GetTeamNumber() == pTeam->GetTeamNumber() ); switch ( pTeam->GetTeamNumber() ) { case TEAM_CT: m_iNumCT++; if ( pPlayer->State_Get() != STATE_PICKINGCLASS ) m_iNumSpawnableCT++; if ( pPlayer->m_lifeState != LIFE_ALIVE ) NumDeadCT++; else NumAliveCT++; break; case TEAM_TERRORIST: m_iNumTerrorist++; if ( pPlayer->State_Get() != STATE_PICKINGCLASS ) m_iNumSpawnableTerrorist++; if ( pPlayer->m_lifeState != LIFE_ALIVE ) NumDeadTerrorist++; else NumAliveTerrorist++; // Check to see if this guy escaped. if ( pPlayer->m_bEscaped == true ) m_iHaveEscaped++; break; } } } } bool CCSGameRules::HostageRescueRoundEndCheck( bool bNeededPlayers ) { // Check to see if 50% of the hostages have been rescued. CHostage* hostage = NULL; int iNumHostages = g_Hostages.Count(); int iNumLeftToRescue = 0; int i; for ( i=0; im_iHealth > 0 && !hostage->IsRescued() ) // We've found a live hostage. don't end the round iNumLeftToRescue++; } m_iHostagesRemaining = iNumLeftToRescue; if ( (iNumLeftToRescue == 0) && (iNumHostages > 0) ) { if ( m_iHostagesRescued >= (iNumHostages * 0.5) ) { m_iAccountCT += 2500; if ( !bNeededPlayers ) { m_iNumCTWins ++; // Update the clients team score UpdateTeamScores(); } CCS_GameStats.Event_AllHostagesRescued(); // tell the bots all the hostages have been rescued IGameEvent * event = gameeventmanager->CreateEvent( "hostage_rescued_all" ); if ( event ) { gameeventmanager->FireEvent( event ); } TerminateRound( mp_round_restart_delay.GetFloat(), All_Hostages_Rescued ); return true; } } return false; } bool CCSGameRules::PrisonRoundEndCheck() { //MIKETODO: get this working when working on prison escape /* if (m_bMapHasEscapeZone == true) { float flEscapeRatio; flEscapeRatio = (float) m_iHaveEscaped / (float) m_iNumEscapers; if (flEscapeRatio >= m_flRequiredEscapeRatio) { BroadcastSound( "Event.TERWin" ); m_iAccountTerrorist += 3150; if ( !bNeededPlayers ) { m_iNumTerroristWins ++; // Update the clients team score UpdateTeamScores(); } EndRoundMessage( "#Terrorists_Escaped", Terrorists_Escaped ); TerminateRound( mp_round_restart_delay.GetFloat(), WINNER_TER ); return; } else if ( NumAliveTerrorist == 0 && flEscapeRatio < m_flRequiredEscapeRatio) { BroadcastSound( "Event.CTWin" ); m_iAccountCT += (1 - flEscapeRatio) * 3500; // CTs are rewarded based on how many terrorists have escaped... if ( !bNeededPlayers ) { m_iNumCTWins++; // Update the clients team score UpdateTeamScores(); } EndRoundMessage( "#CTs_PreventEscape", CTs_PreventEscape ); TerminateRound( mp_round_restart_delay.GetFloat(), WINNER_CT ); return; } else if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0 ) { BroadcastSound( "Event.CTWin" ); m_iAccountCT += (1 - flEscapeRatio) * 3250; // CTs are rewarded based on how many terrorists have escaped... if ( !bNeededPlayers ) { m_iNumCTWins++; // Update the clients team score UpdateTeamScores(); } EndRoundMessage( "#Escaping_Terrorists_Neutralized", Escaping_Terrorists_Neutralized ); TerminateRound( mp_round_restart_delay.GetFloat(), WINNER_CT ); return; } // else return; } */ return false; } bool CCSGameRules::VIPRoundEndCheck( bool bNeededPlayers ) { if (m_iMapHasVIPSafetyZone != 1) return false; if (m_pVIP == NULL) return false; if (m_pVIP->m_bEscaped == true) { m_iAccountCT += 3500; if ( !bNeededPlayers ) { m_iNumCTWins ++; // Update the clients team score UpdateTeamScores(); } //MIKETODO: get this working when working on VIP scenarios /* MessageBegin( MSG_SPEC, SVC_DIRECTOR ); WRITE_BYTE ( 9 ); // command length in bytes WRITE_BYTE ( DRC_CMD_EVENT ); // VIP rescued WRITE_SHORT( ENTINDEX(m_pVIP->edict()) ); // index number of primary entity WRITE_SHORT( 0 ); // index number of secondary entity WRITE_LONG( 15 | DRC_FLAG_FINAL); // eventflags (priority and flags) MessageEnd(); */ // tell the bots the VIP got out IGameEvent * event = gameeventmanager->CreateEvent( "vip_escaped" ); if ( event ) { event->SetInt( "userid", m_pVIP->GetUserID() ); event->SetInt( "priority", 9 ); gameeventmanager->FireEvent( event ); } //============================================================================= // HPE_BEGIN: // [menglish] If the VIP has escaped award him an MVP //============================================================================= m_pVIP->IncrementNumMVPs( CSMVP_UNDEFINED ); //============================================================================= // HPE_END //============================================================================= TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Escaped ); return true; } else if ( m_pVIP->m_lifeState == LIFE_DEAD ) // The VIP is dead { m_iAccountTerrorist += 3250; if ( !bNeededPlayers ) { m_iNumTerroristWins ++; // Update the clients team score UpdateTeamScores(); } // tell the bots the VIP was killed IGameEvent * event = gameeventmanager->CreateEvent( "vip_killed" ); if ( event ) { event->SetInt( "userid", m_pVIP->GetUserID() ); event->SetInt( "priority", 9 ); gameeventmanager->FireEvent( event ); } TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Assassinated ); return true; } return false; } bool CCSGameRules::BombRoundEndCheck( bool bNeededPlayers ) { // Check to see if the bomb target was hit or the bomb defused.. if so, then let's end the round! if ( ( m_bTargetBombed == true ) && ( m_bMapHasBombTarget == true ) ) { m_iAccountTerrorist += 3500; if ( !bNeededPlayers ) { m_iNumTerroristWins ++; // Update the clients team score UpdateTeamScores(); } TerminateRound( mp_round_restart_delay.GetFloat(), Target_Bombed ); return true; } else if ( ( m_bBombDefused == true ) && ( m_bMapHasBombTarget == true ) ) { m_iAccountCT += 3250; m_iAccountTerrorist += 800; // give the T's a little bonus for planting the bomb even though it was defused. if ( !bNeededPlayers ) { m_iNumCTWins++; // Update the clients team score UpdateTeamScores(); } TerminateRound( mp_round_restart_delay.GetFloat(), Bomb_Defused ); return true; } return false; } bool CCSGameRules::TeamExterminationCheck( int NumAliveTerrorist, int NumAliveCT, int NumDeadTerrorist, int NumDeadCT, bool bNeededPlayers ) { if ( ( m_iNumCT > 0 && m_iNumSpawnableCT > 0 ) && ( m_iNumTerrorist > 0 && m_iNumSpawnableTerrorist > 0 ) ) { if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0 ) { bool nowin = false; for ( int iGrenade=0; iGrenade < g_PlantedC4s.Count(); iGrenade++ ) { CPlantedC4 *pC4 = g_PlantedC4s[iGrenade]; if ( pC4->IsBombActive() ) nowin = true; } if ( !nowin ) { if ( m_bMapHasBombTarget ) m_iAccountCT += 3250; else m_iAccountCT += 3000; if ( !bNeededPlayers ) { m_iNumCTWins++; // Update the clients team score UpdateTeamScores(); } TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win ); return true; } } // Terrorists WON if ( NumAliveCT == 0 && NumDeadCT != 0 && m_iNumSpawnableTerrorist > 0 ) { if ( m_bMapHasBombTarget ) m_iAccountTerrorist += 3250; else m_iAccountTerrorist += 3000; if ( !bNeededPlayers ) { m_iNumTerroristWins++; // Update the clients team score UpdateTeamScores(); } TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win ); return true; } } else if ( NumAliveCT == 0 && NumAliveTerrorist == 0 ) { TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw ); return true; } return false; } void CCSGameRules::PickNextVIP() { // MIKETODO: work on this when getting VIP maps running. /* if (IsVIPQueueEmpty() != true) { // Remove the current VIP from his VIP status and make him a regular CT. if (m_pVIP != NULL) ResetCurrentVIP(); for (int i = 0; i <= 4; i++) { if (VIPQueue[i] != NULL) { m_pVIP = VIPQueue[i]; m_pVIP->MakeVIP(); VIPQueue[i] = NULL; // remove this player from the VIP queue StackVIPQueue(); // and re-organize the queue m_iConsecutiveVIP = 0; return; } } } else if (m_iConsecutiveVIP >= 3) // If it's been the same VIP for 3 rounds already.. then randomly pick a new one { m_iLastPick++; if (m_iLastPick > m_iNumCT) m_iLastPick = 1; int iCount = 1; CBaseEntity* pPlayer = NULL; CBasePlayer* player = NULL; CBasePlayer* pLastPlayer = NULL; pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) ) { if ( !(pPlayer->pev->flags & FL_DORMANT) ) { player = GetClassPtr((CBasePlayer *)pPlayer->pev); if ( (player->m_iTeam == CT) && (iCount == m_iLastPick) ) { if ( (player == m_pVIP) && (pLastPlayer != NULL) ) player = pLastPlayer; // Remove the current VIP from his VIP status and make him a regular CT. if (m_pVIP != NULL) ResetCurrentVIP(); player->MakeVIP(); m_iConsecutiveVIP = 0; return; } else if ( player->m_iTeam == CT ) iCount++; if ( player->m_iTeam != SPECTATOR ) pLastPlayer = player; } pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); } } else if (m_pVIP == NULL) // There is no VIP and there is no one waiting to be the VIP.. therefore just pick the first CT player we can find. { CBaseEntity* pPlayer = NULL; CBasePlayer* player = NULL; pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) ) { if ( pPlayer->pev->flags != FL_DORMANT ) { player = GetClassPtr((CBasePlayer *)pPlayer->pev); if ( player->m_iTeam == CT ) { player->MakeVIP(); m_iConsecutiveVIP = 0; return; } } pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); } } */ } void CCSGameRules::ReadMultiplayCvars() { m_iRoundTime = (int)(mp_roundtime.GetFloat() * 60); m_iFreezeTime = mp_freezetime.GetInt(); } void CCSGameRules::RestartRound() { #if defined( REPLAY_ENABLED ) if ( g_pReplay ) { // Write replay and stop recording if appropriate if ( g_pReplay->IsRecording() ) { g_pReplay->SV_EndRecordingSession(); } int nActivePlayerCount = m_iNumTerrorist + m_iNumCT; if ( nActivePlayerCount && g_pReplay->SV_ShouldBeginRecording( false ) ) { // Tell the replay manager that it should begin recording the new round as soon as possible g_pReplay->SV_GetContext()->GetSessionRecorder()->StartRecording(); } } #endif //============================================================================= // HPE_BEGIN: // [tj] Notify players that the round is about to be reset //============================================================================= for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( clientIndex ); if(pPlayer) { pPlayer->OnPreResetRound(); } } //============================================================================= // HPE_END //============================================================================= if ( !IsFinite( gpGlobals->curtime ) ) { Warning( "NaN curtime in RestartRound\n" ); gpGlobals->curtime = 0.0f; } int i; m_iTotalRoundsPlayed++; //ClearBodyQue(); // Hardlock the player accelaration to 5.0 //CVAR_SET_FLOAT( "sv_accelerate", 5.0 ); //CVAR_SET_FLOAT( "sv_friction", 4.0 ); //CVAR_SET_FLOAT( "sv_stopspeed", 75 ); sv_stopspeed.SetValue( 75.0f ); // Tabulate the number of players on each team. int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); m_bBombDropped = false; m_bBombPlanted = false; if ( GetHumanTeam() != TEAM_UNASSIGNED ) { MoveHumansToHumanTeam(); } /*************** AUTO-BALANCE CODE *************/ if ( mp_autoteambalance.GetInt() != 0 && (m_iUnBalancedRounds >= 1) ) { if ( GetHumanTeam() == TEAM_UNASSIGNED ) { BalanceTeams(); } } if ( ((m_iNumSpawnableCT - m_iNumSpawnableTerrorist) >= 2) || ((m_iNumSpawnableTerrorist - m_iNumSpawnableCT) >= 2) ) { m_iUnBalancedRounds++; } else { m_iUnBalancedRounds = 0; } // Warn the players of an impending auto-balance next round... if ( mp_autoteambalance.GetInt() != 0 && (m_iUnBalancedRounds == 1) ) { if ( GetHumanTeam() == TEAM_UNASSIGNED ) { UTIL_ClientPrintAll( HUD_PRINTCENTER,"#Auto_Team_Balance_Next_Round"); } } /*************** AUTO-BALANCE CODE *************/ if ( m_bCompleteReset ) { // bounds check if ( mp_timelimit.GetInt() < 0 ) { mp_timelimit.SetValue( 0 ); } m_flGameStartTime = gpGlobals->curtime; if ( !IsFinite( m_flGameStartTime.Get() ) ) { Warning( "Trying to set a NaN game start time\n" ); m_flGameStartTime.GetForModify() = 0.0f; } // Reset total # of rounds played m_iTotalRoundsPlayed = 0; // Reset score info m_iNumTerroristWins = 0; m_iNumCTWins = 0; m_iNumConsecutiveTerroristLoses = 0; m_iNumConsecutiveCTLoses = 0; // Reset team scores UpdateTeamScores(); // Reset the player stats for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = CCSPlayer::Instance( i ); if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) pPlayer->Reset(); } } m_bFreezePeriod = true; ReadMultiplayCvars(); // Check to see if there's a mapping info paramater entity if ( g_pMapInfo ) { switch ( g_pMapInfo->m_iBuyingStatus ) { case 0: m_bCTCantBuy = false; m_bTCantBuy = false; Msg( "EVERYONE CAN BUY!\n" ); break; case 1: m_bCTCantBuy = false; m_bTCantBuy = true; Msg( "Only CT's can buy!!\n" ); break; case 2: m_bCTCantBuy = true; m_bTCantBuy = false; Msg( "Only T's can buy!!\n" ); break; case 3: m_bCTCantBuy = true; m_bTCantBuy = true; Msg( "No one can buy!!\n" ); break; default: m_bCTCantBuy = false; m_bTCantBuy = false; break; } } else { // by default everyone can buy m_bCTCantBuy = false; m_bTCantBuy = false; } // Check to see if this map has a bomb target in it if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) ) { m_bMapHasBombTarget = true; m_bMapHasBombZone = true; } else if ( gEntList.FindEntityByClassname( NULL, "info_bomb_target" ) ) { m_bMapHasBombTarget = true; m_bMapHasBombZone = false; } else { m_bMapHasBombTarget = false; m_bMapHasBombZone = false; } // Check to see if this map has hostage rescue zones if ( gEntList.FindEntityByClassname( NULL, "func_hostage_rescue" ) ) m_bMapHasRescueZone = true; else m_bMapHasRescueZone = false; // See if the map has func_buyzone entities // Used by CBasePlayer::HandleSignals() to support maps without these entities if ( gEntList.FindEntityByClassname( NULL, "func_buyzone" ) ) m_bMapHasBuyZone = true; else m_bMapHasBuyZone = false; // GOOSEMAN : See if this map has func_escapezone entities if ( gEntList.FindEntityByClassname( NULL, "func_escapezone" ) ) { m_bMapHasEscapeZone = true; m_iHaveEscaped = 0; m_iNumEscapers = 0; // Will increase this later when we count how many Ts are starting if (m_iNumEscapeRounds >= 3) { SwapAllPlayers(); m_iNumEscapeRounds = 0; } m_iNumEscapeRounds++; // Increment the number of rounds played... After 8 rounds, the players will do a whole sale switch.. } else m_bMapHasEscapeZone = false; // Check to see if this map has VIP safety zones if ( gEntList.FindEntityByClassname( NULL, "func_vip_safetyzone" ) ) { PickNextVIP(); m_iConsecutiveVIP++; m_iMapHasVIPSafetyZone = 1; } else m_iMapHasVIPSafetyZone = 2; // Update accounts based on number of hostages remaining.. int iRescuedHostageBonus = 0; for ( int iHostage=0; iHostage < g_Hostages.Count(); iHostage++ ) { CHostage *pHostage = g_Hostages[iHostage]; if( pHostage->IsRescuable() ) //Alive and not rescued { iRescuedHostageBonus += 150; } if ( iRescuedHostageBonus >= 2000 ) break; } //*******Catch up code by SupraFiend. Scale up the loser bonus when teams fall into losing streaks if (m_iRoundWinStatus == WINNER_TER) // terrorists won { //check to see if they just broke a losing streak if(m_iNumConsecutiveTerroristLoses > 1) m_iLoserBonus = 1500;//this is the default losing bonus m_iNumConsecutiveTerroristLoses = 0;//starting fresh m_iNumConsecutiveCTLoses++;//increment the number of wins the CTs have had } else if (m_iRoundWinStatus == WINNER_CT) // CT Won { //check to see if they just broke a losing streak if(m_iNumConsecutiveCTLoses > 1) m_iLoserBonus = 1500;//this is the default losing bonus m_iNumConsecutiveCTLoses = 0;//starting fresh m_iNumConsecutiveTerroristLoses++;//increment the number of wins the Terrorists have had } //check if the losing team is in a losing streak & that the loser bonus hasen't maxed out. if((m_iNumConsecutiveTerroristLoses > 1) && (m_iLoserBonus < 3000)) m_iLoserBonus += 500;//help out the team in the losing streak else if((m_iNumConsecutiveCTLoses > 1) && (m_iLoserBonus < 3000)) m_iLoserBonus += 500;//help out the team in the losing streak // assign the wining and losing bonuses if (m_iRoundWinStatus == WINNER_TER) // terrorists won { m_iAccountTerrorist += iRescuedHostageBonus; m_iAccountCT += m_iLoserBonus; } else if (m_iRoundWinStatus == WINNER_CT) // CT Won { m_iAccountCT += iRescuedHostageBonus; if (m_bMapHasEscapeZone == false) // only give them the bonus if this isn't an escape map m_iAccountTerrorist += m_iLoserBonus; } //Update CT account based on number of hostages rescued m_iAccountCT += m_iHostagesRescued * 750; // Update individual players accounts and respawn players //**********new code by SupraFiend //##########code changed by MartinO //the round time stamp must be set before players are spawned m_fRoundStartTime = gpGlobals->curtime + m_iFreezeTime; if ( !IsFinite( m_fRoundStartTime.Get() ) ) { Warning( "Trying to set a NaN round start time\n" ); m_fRoundStartTime.GetForModify() = 0.0f; } //Adrian - No cash for anyone at first rounds! ( well, only the default. ) if ( m_bCompleteReset ) { m_iAccountTerrorist = m_iAccountCT = 0; //No extra cash!. //We are starting fresh. So it's like no one has ever won or lost. m_iNumTerroristWins = 0; m_iNumCTWins = 0; m_iNumConsecutiveTerroristLoses = 0; m_iNumConsecutiveCTLoses = 0; m_iLoserBonus = 1400; } for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; pPlayer->m_iNumSpawns = 0; pPlayer->m_bTeamChanged = false; if ( pPlayer->GetTeamNumber() == TEAM_CT ) { if (pPlayer->DoesPlayerGetRoundStartMoney()) { pPlayer->AddAccount( m_iAccountCT ); } } else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) { m_iNumEscapers++; // Add another potential escaper to the mix! if (pPlayer->DoesPlayerGetRoundStartMoney()) { pPlayer->AddAccount( m_iAccountTerrorist ); } } // tricky, make players non solid while moving to their spawn points if ( (pPlayer->GetTeamNumber() == TEAM_CT) || (pPlayer->GetTeamNumber() == TEAM_TERRORIST) ) { pPlayer->AddSolidFlags( FSOLID_NOT_SOLID ); } } //============================================================================= // HPE_BEGIN: // [tj] Keep track of number of players per side and if they have the same uniform //============================================================================= int terroristUniform = -1; bool allTerroristsWearingSameUniform = true; int numberOfTerrorists = 0; int ctUniform = -1; bool allCtsWearingSameUniform = true; int numberOfCts = 0; //============================================================================= // HPE_END //============================================================================= // know respawn all players for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; if ( pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->PlayerClass() >= FIRST_CT_CLASS && pPlayer->PlayerClass() <= LAST_CT_CLASS ) { //============================================================================= // HPE_BEGIN: // [tj] Increment CT count and check CT uniforms. //============================================================================= numberOfCts++; if (ctUniform == -1) { ctUniform = pPlayer->PlayerClass(); } else if (pPlayer->PlayerClass() != ctUniform) { allCtsWearingSameUniform = false; } //============================================================================= // HPE_END //============================================================================= pPlayer->RoundRespawn(); } if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS ) { //============================================================================= // HPE_BEGIN: // [tj] Increment terrorist count and check terrorist uniforms //============================================================================= numberOfTerrorists++; if (terroristUniform == -1) { terroristUniform = pPlayer->PlayerClass(); } else if (pPlayer->PlayerClass() != terroristUniform) { allTerroristsWearingSameUniform = false; } //============================================================================= // HPE_END //============================================================================= pPlayer->RoundRespawn(); } else { pPlayer->ObserverRoundRespawn(); } if ( pPlayer->m_iAccount > pPlayer->m_iShouldHaveCash ) { m_bDontUploadStats = true; } } //============================================================================= // HPE_BEGIN: //============================================================================= // [tj] Award same uniform achievement for qualifying teams for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; if ( pPlayer->GetTeamNumber() == TEAM_CT && allCtsWearingSameUniform && numberOfCts >= AchievementConsts::SameUniform_MinPlayers) { pPlayer->AwardAchievement(CSSameUniform); } if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && allTerroristsWearingSameUniform && numberOfTerrorists >= AchievementConsts::SameUniform_MinPlayers) { pPlayer->AwardAchievement(CSSameUniform); } } // [menglish] reset per-round achievement variables for each player for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if( pPlayer ) { pPlayer->ResetRoundBasedAchievementVariables(); } } // [pfreese] Reset all round or match stats, depending on type of restart if ( m_bCompleteReset ) { CCS_GameStats.ResetAllStats(); CCS_GameStats.ResetPlayerClassMatchStats(); } else { CCS_GameStats.ResetRoundStats(); } //============================================================================= // HPE_END //============================================================================= // Respawn entities (glass, doors, etc..) CleanUpMap(); // now run a tkpunish check, after the map has been cleaned up for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; if ( pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->PlayerClass() >= FIRST_CT_CLASS && pPlayer->PlayerClass() <= LAST_CT_CLASS ) { pPlayer->CheckTKPunishment(); } if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS ) { pPlayer->CheckTKPunishment(); } } // Give C4 to the terrorists if (m_bMapHasBombTarget == true ) GiveC4(); // Reset game variables m_flIntermissionEndTime = 0; m_flRestartRoundTime = 0.0; m_iAccountTerrorist = m_iAccountCT = 0; m_iHostagesRescued = 0; m_iHostagesTouched = 0; //============================================================================= // HPE_BEGIN // [dwenger] Reset rescue-related achievement values //============================================================================= // [tj] reset flawless and lossless round related flags m_bNoTerroristsKilled = true; m_bNoCTsKilled = true; m_bNoTerroristsDamaged = true; m_bNoCTsDamaged = true; m_pFirstKill = NULL; m_pFirstBlood = NULL; m_bCanDonateWeapons = true; // [dwenger] Reset rescue-related achievement values m_iHostagesRemaining = 0; m_pLastRescuer = NULL; m_hostageWasInjured = false; m_hostageWasKilled = false; //============================================================================= // HPE_END //============================================================================= m_iNumRescuers = 0; m_iRoundWinStatus = WINNER_NONE; m_bTargetBombed = m_bBombDefused = false; m_bCompleteReset = false; m_flNextHostageAnnouncement = gpGlobals->curtime; m_iHostagesRemaining = g_Hostages.Count(); // fire global game event IGameEvent * event = gameeventmanager->CreateEvent( "round_start" ); if ( event ) { event->SetInt("timelimit", m_iRoundTime ); event->SetInt("fraglimit", 0 ); event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted if ( m_bMapHasRescueZone ) { event->SetString("objective","HOSTAGE RESCUE"); } else if ( m_bMapHasEscapeZone ) { event->SetString("objective","PRISON ESCAPE"); } else if ( m_iMapHasVIPSafetyZone == 1 ) { event->SetString("objective","VIP RESCUE"); } else if ( m_bMapHasBombTarget || m_bMapHasBombZone ) { event->SetString("objective","BOMB TARGET"); } else { event->SetString("objective","DEATHMATCH"); } gameeventmanager->FireEvent( event ); } UploadGameStats(); //============================================================================= // HPE_BEGIN: // [pfreese] I commented out this call to CreateWeaponManager, as the // CGameWeaponManager object doesn't appear to be actually used by the CSS // code, and in any case, the weapon manager does not support wildcards in // entity names (as seemingly indicated) below. When the manager fails to // create its factory, it removes itself in any case. //============================================================================= // CreateWeaponManager( "weapon_*", gpGlobals->maxClients * 2 ); //============================================================================= // HPE_END //============================================================================= } void CCSGameRules::GiveC4() { enum { ALL_TERRORISTS = 0, HUMAN_TERRORISTS, }; int iTerrorists[2][ABSOLUTE_PLAYER_LIMIT]; int numAliveTs[2] = { 0, 0 }; int lastBombGuyIndex[2] = { -1, -1 }; //Create an array of the indeces of bomb carrier candidates for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) ); if( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == TEAM_TERRORIST && numAliveTs[ALL_TERRORISTS] < ABSOLUTE_PLAYER_LIMIT ) { if ( pPlayer == m_pLastBombGuy ) { lastBombGuyIndex[ALL_TERRORISTS] = numAliveTs[ALL_TERRORISTS]; lastBombGuyIndex[HUMAN_TERRORISTS] = numAliveTs[HUMAN_TERRORISTS]; } iTerrorists[ALL_TERRORISTS][numAliveTs[ALL_TERRORISTS]] = i; numAliveTs[ALL_TERRORISTS]++; if ( !pPlayer->IsBot() ) { iTerrorists[HUMAN_TERRORISTS][numAliveTs[HUMAN_TERRORISTS]] = i; numAliveTs[HUMAN_TERRORISTS]++; } } } int which = cv_bot_defer_to_human.GetBool(); if ( numAliveTs[HUMAN_TERRORISTS] == 0 ) { which = ALL_TERRORISTS; } //pick one of the candidates randomly if( numAliveTs[which] > 0 ) { int index = random->RandomInt(0,numAliveTs[which]-1); if ( lastBombGuyIndex[which] >= 0 ) { // give the C4 sequentially index = (lastBombGuyIndex[which] + 1) % numAliveTs[which]; } CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iTerrorists[which][index] ) ); Assert( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->IsAlive() ); pPlayer->GiveNamedItem( WEAPON_C4_CLASSNAME ); m_pLastBombGuy = pPlayer; //pPlayer->SetBombIcon(); //pPlayer->pev->body = 1; pPlayer->m_iDisplayHistoryBits |= DHF_BOMB_RETRIEVED; pPlayer->HintMessage( "#Hint_you_have_the_bomb", false, true ); // Log this information //UTIL_LogPrintf("\"%s<%i><%s>\" triggered \"Spawned_With_The_Bomb\"\n", // STRING( pPlayer->GetPlayerName() ), // GETPLAYERUSERID( pPlayer->edict() ), // GETPLAYERAUTHID( pPlayer->edict() ) ); } m_bBombDropped = false; } void CCSGameRules::Think() { CGameRules::Think(); for ( int i = 0; i < GetNumberOfTeams(); i++ ) { GetGlobalTeam( i )->Think(); } ///// Check game rules ///// if ( CheckGameOver() ) { return; } // have we hit the max rounds? if ( CheckMaxRounds() ) { return; } // did somebaody hit the fraglimit ? if ( CheckFragLimit() ) { return; } if ( CheckWinLimit() ) { return; } // Check for the end of the round. if ( IsFreezePeriod() ) { CheckFreezePeriodExpired(); } else { CheckRoundTimeExpired(); } CheckLevelInitialized(); if ( m_flRestartRoundTime > 0.0f && m_flRestartRoundTime <= gpGlobals->curtime ) { bool botSpeaking = false; for ( int i=1; i <= gpGlobals->maxClients; ++i ) { CBasePlayer *player = UTIL_PlayerByIndex( i ); if (player == NULL) continue; if (!player->IsBot()) continue; CCSBot *bot = dynamic_cast< CCSBot * >(player); if ( !bot ) continue; if ( bot->IsUsingVoice() ) { if ( gpGlobals->curtime > m_flRestartRoundTime + 10.0f ) { Msg( "Ignoring speaking bot %s at round end\n", bot->GetPlayerName() ); } else { botSpeaking = true; break; } } } if ( !botSpeaking ) { RestartRound(); } } if ( gpGlobals->curtime > m_tmNextPeriodicThink ) { CheckRestartRound(); m_tmNextPeriodicThink = gpGlobals->curtime + 1.0; } } // The bots do their processing after physics simulation etc so their visibility checks don't recompute // bone positions multiple times a frame. void CCSGameRules::EndGameFrame( void ) { TheBots->StartFrame(); BaseClass::EndGameFrame(); } bool CCSGameRules::CheckGameOver() { if ( g_fGameOver ) // someone else quit the game already { //============================================================================= // HPE_BEGIN: // [Forrest] Calling ChangeLevel multiple times was causing IncrementMapCycleIndex // to skip over maps in the list. Avoid this using a technique from CTeamplayRoundBasedRules::Think. //============================================================================= // check to see if we should change levels now if ( m_flIntermissionEndTime && ( m_flIntermissionEndTime < gpGlobals->curtime ) ) { ChangeLevel(); // intermission is over // Don't run this code again m_flIntermissionEndTime = 0.f; } //============================================================================= // HPE_END //============================================================================= return true; } return false; } bool CCSGameRules::CheckFragLimit() { if ( fraglimit.GetInt() <= 0 ) return false; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer && pPlayer->FragCount() >= fraglimit.GetInt() ) { const char *teamName = "UNKNOWN"; if ( pPlayer->GetTeam() ) { teamName = pPlayer->GetTeam()->GetName(); } UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"Intermission_Kill_Limit\"\n", pPlayer->GetPlayerName(), pPlayer->GetUserID(), pPlayer->GetNetworkIDString(), teamName ); GoToIntermission(); return true; } } return false; } bool CCSGameRules::CheckMaxRounds() { if ( mp_maxrounds.GetInt() != 0 ) { if ( m_iTotalRoundsPlayed >= mp_maxrounds.GetInt() ) { UTIL_LogPrintf("World triggered \"Intermission_Round_Limit\"\n"); GoToIntermission(); return true; } } return false; } bool CCSGameRules::CheckWinLimit() { // has one team won the specified number of rounds? if ( mp_winlimit.GetInt() != 0 ) { if ( m_iNumCTWins >= mp_winlimit.GetInt() ) { UTIL_LogPrintf("Team \"CT\" triggered \"Intermission_Win_Limit\"\n"); GoToIntermission(); return true; } if ( m_iNumTerroristWins >= mp_winlimit.GetInt() ) { UTIL_LogPrintf("Team \"TERRORIST\" triggered \"Intermission_Win_Limit\"\n"); GoToIntermission(); return true; } } return false; } void CCSGameRules::CheckFreezePeriodExpired() { float startTime = m_fRoundStartTime; if ( !IsFinite( startTime ) ) { Warning( "Infinite round start time!\n" ); m_fRoundStartTime.GetForModify() = gpGlobals->curtime; } if ( IsFinite( startTime ) && gpGlobals->curtime < startTime ) { return; // not time yet to start round } // Log this information UTIL_LogPrintf("World triggered \"Round_Start\"\n"); char CT_sentence[40]; char T_sentence[40]; switch ( random->RandomInt( 0, 3 ) ) { case 0: Q_strncpy(CT_sentence,"radio.moveout", sizeof( CT_sentence ) ); Q_strncpy(T_sentence ,"radio.moveout", sizeof( T_sentence ) ); break; case 1: Q_strncpy(CT_sentence, "radio.letsgo", sizeof( CT_sentence ) ); Q_strncpy(T_sentence , "radio.letsgo", sizeof( T_sentence ) ); break; case 2: Q_strncpy(CT_sentence , "radio.locknload", sizeof( CT_sentence ) ); Q_strncpy(T_sentence , "radio.locknload", sizeof( T_sentence ) ); break; default: Q_strncpy(CT_sentence , "radio.go", sizeof( CT_sentence ) ); Q_strncpy(T_sentence , "radio.go", sizeof( T_sentence ) ); break; } // More specific radio commands for the new scenarios : Prison & Assasination if (m_bMapHasEscapeZone == TRUE) { Q_strncpy(CT_sentence , "radio.elim", sizeof( CT_sentence ) ); Q_strncpy(T_sentence , "radio.getout", sizeof( T_sentence ) ); } else if (m_iMapHasVIPSafetyZone == 1) { Q_strncpy(CT_sentence , "radio.vip", sizeof( CT_sentence ) ); Q_strncpy(T_sentence , "radio.locknload", sizeof( T_sentence ) ); } // Freeze period expired: kill the flag m_bFreezePeriod = false; IGameEvent * event = gameeventmanager->CreateEvent( "round_freeze_end" ); if ( event ) { gameeventmanager->FireEvent( event ); } // Update the timers for all clients and play a sound bool bCTPlayed = false; bool bTPlayed = false; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = CCSPlayer::Instance( i ); if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) { if ( pPlayer->State_Get() == STATE_ACTIVE ) { if ( (pPlayer->GetTeamNumber() == TEAM_CT) && !bCTPlayed ) { pPlayer->Radio( CT_sentence ); bCTPlayed = true; } else if ( (pPlayer->GetTeamNumber() == TEAM_TERRORIST) && !bTPlayed ) { pPlayer->Radio( T_sentence ); bTPlayed = true; } } //pPlayer->SyncRoundTimer(); } } } void CCSGameRules::CheckRoundTimeExpired() { if ( mp_ignore_round_win_conditions.GetBool() ) return; if ( GetRoundRemainingTime() > 0 || m_iRoundWinStatus != WINNER_NONE ) return; //We haven't completed other objectives, so go for this!. if( !m_bFirstConnected ) return; // New code to get rid of round draws!! if ( m_bMapHasBombTarget ) { //If the bomb is planted, don't let the round timer end the round. //keep going until the bomb explodes or is defused if( !m_bBombPlanted ) { m_iAccountCT += 3250; m_iNumCTWins++; TerminateRound( mp_round_restart_delay.GetFloat(), Target_Saved ); UpdateTeamScores(); MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_TERRORIST); } } else if ( m_bMapHasRescueZone ) { m_iAccountTerrorist += 3250; m_iNumTerroristWins++; TerminateRound( mp_round_restart_delay.GetFloat(), Hostages_Not_Rescued ); UpdateTeamScores(); MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_CT); } else if ( m_bMapHasEscapeZone ) { m_iNumCTWins++; TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Not_Escaped ); UpdateTeamScores(); } else if ( m_iMapHasVIPSafetyZone == 1 ) { m_iAccountTerrorist += 3250; m_iNumTerroristWins++; TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Not_Escaped ); UpdateTeamScores(); } #if defined( REPLAY_ENABLED ) if ( g_pReplay ) { // Write replay and stop recording if appropriate g_pReplay->SV_EndRecordingSession(); } #endif } void CCSGameRules::GoToIntermission( void ) { Msg( "Going to intermission...\n" ); IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" ); if( winEvent ) { for ( int teamIndex = TEAM_TERRORIST; teamIndex <= TEAM_CT; teamIndex++ ) { CTeam *team = GetGlobalTeam( teamIndex ); if ( team ) { float kills = CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_KILLS]; float deaths = CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_DEATHS]; // choose dialog variables to set depending on team switch ( teamIndex ) { case TEAM_TERRORIST: winEvent->SetInt( "t_score", team->GetScore() ); if(deaths == 0) { winEvent->SetFloat( "t_kd", kills ); } else { winEvent->SetFloat( "t_kd", kills / deaths ); } winEvent->SetInt( "t_objectives_done", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_OBJECTIVES_COMPLETED] ); winEvent->SetInt( "t_money_earned", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_MONEY_EARNED] ); break; case TEAM_CT: winEvent->SetInt( "ct_score", team->GetScore() ); if(deaths == 0) { winEvent->SetFloat( "ct_kd", kills ); } else { winEvent->SetFloat( "ct_kd", kills / deaths ); } winEvent->SetInt( "ct_objectives_done", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_OBJECTIVES_COMPLETED] ); winEvent->SetInt( "ct_money_earned", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_MONEY_EARNED] ); break; default: Assert( false ); break; } } } gameeventmanager->FireEvent( winEvent ); } BaseClass::GoToIntermission(); // set all players to FL_FROZEN for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer ) { pPlayer->AddFlag( FL_FROZEN ); } } // freeze players while in intermission m_bFreezePeriod = true; } 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; } #if defined (_DEBUG) void TestRoundWinpanel( void ) { IGameEvent *event = gameeventmanager->CreateEvent( "round_end" ); event->SetInt( "winner", TEAM_TERRORIST ); if ( event ) { gameeventmanager->FireEvent( event ); } IGameEvent *event2 = gameeventmanager->CreateEvent( "player_death" ); if ( event2 ) { CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex(1) ); // pCappingPlayers is a null terminated list of player indeces event2->SetInt("userid", pPlayer->GetUserID() ); event2->SetInt("attacker", pPlayer->GetUserID() ); event2->SetString("weapon", "Bare Hands" ); event2->SetInt("headshot", 1 ); event2->SetInt( "revenge", 1 ); gameeventmanager->FireEvent( event2 ); } IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_round" ); 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 = Terrorists_Win; winEvent->SetInt( "final_event", iLastEvent ); // Set the fun fact data in the event winEvent->SetString( "funfact_token", "#funfact_first_blood" ); winEvent->SetInt( "funfact_player", 1 ); winEvent->SetInt( "funfact_data1", 20 ); winEvent->SetInt( "funfact_data2", 31 ); winEvent->SetInt( "funfact_data3", 45 ); gameeventmanager->FireEvent( winEvent ); } } ConCommand test_round_winpanel( "test_round_winpanel", TestRoundWinpanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); void TestMatchWinpanel( void ) { IGameEvent *event = gameeventmanager->CreateEvent( "round_end" ); event->SetInt( "winner", TEAM_TERRORIST ); if ( event ) { gameeventmanager->FireEvent( event ); } IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" ); if ( winEvent ) { winEvent->SetInt( "t_score", 4 ); winEvent->SetInt( "ct_score", 1 ); winEvent->SetFloat( "t_kd", 1.8f ); winEvent->SetFloat( "ct_kd", 0.4f ); winEvent->SetInt( "t_objectives_done", 5 ); winEvent->SetInt( "ct_objectives_done", 2 ); winEvent->SetInt( "t_money_earned", 30000 ); winEvent->SetInt( "ct_money_earned", 19999 ); gameeventmanager->FireEvent( winEvent ); } } ConCommand test_match_winpanel( "test_match_winpanel", TestMatchWinpanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); void TestFreezePanel( void ) { IGameEvent *event = gameeventmanager->CreateEvent( "freezecam_started" ); if ( event ) { gameeventmanager->FireEvent( event ); } IGameEvent *winEvent = gameeventmanager->CreateEvent( "show_freezepanel" ); if ( winEvent ) { winEvent->SetInt( "killer", 1 ); gameeventmanager->FireEvent( winEvent ); } } ConCommand test_freezepanel( "test_freezepanel", TestFreezePanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); #endif // _DEBUG static void PrintToConsole( CBasePlayer *player, const char *text ) { if ( player ) { ClientPrint( player, HUD_PRINTCONSOLE, text ); } else { Msg( "%s", text ); } } void CCSGameRules::DumpTimers( void ) const { extern ConVar bot_join_delay; CBasePlayer *player = UTIL_GetCommandClient(); CFmtStr str; PrintToConsole( player, str.sprintf( "Timers and related info at %f:\n", gpGlobals->curtime ) ); PrintToConsole( player, str.sprintf( "m_bCompleteReset: %d\n", m_bCompleteReset ) ); PrintToConsole( player, str.sprintf( "m_iTotalRoundsPlayed: %d\n", m_iTotalRoundsPlayed ) ); PrintToConsole( player, str.sprintf( "m_iRoundTime: %d\n", m_iRoundTime.Get() ) ); PrintToConsole( player, str.sprintf( "m_iRoundWinStatus: %d\n", m_iRoundWinStatus ) ); PrintToConsole( player, str.sprintf( "first connected: %d\n", m_bFirstConnected ) ); PrintToConsole( player, str.sprintf( "intermission end time: %f\n", m_flIntermissionEndTime ) ); PrintToConsole( player, str.sprintf( "freeze period: %d\n", m_bFreezePeriod.Get() ) ); PrintToConsole( player, str.sprintf( "round restart time: %f\n", m_flRestartRoundTime ) ); PrintToConsole( player, str.sprintf( "game start time: %f\n", m_flGameStartTime.Get() ) ); PrintToConsole( player, str.sprintf( "m_fRoundStartTime: %f\n", m_fRoundStartTime.Get() ) ); PrintToConsole( player, str.sprintf( "freeze time: %d\n", m_iFreezeTime ) ); PrintToConsole( player, str.sprintf( "next think: %f\n", m_tmNextPeriodicThink ) ); PrintToConsole( player, str.sprintf( "fraglimit: %d\n", fraglimit.GetInt() ) ); PrintToConsole( player, str.sprintf( "mp_maxrounds: %d\n", mp_maxrounds.GetInt() ) ); PrintToConsole( player, str.sprintf( "mp_winlimit: %d\n", mp_winlimit.GetInt() ) ); PrintToConsole( player, str.sprintf( "bot_quota: %d\n", cv_bot_quota.GetInt() ) ); PrintToConsole( player, str.sprintf( "bot_quota_mode: %s\n", cv_bot_quota_mode.GetString() ) ); PrintToConsole( player, str.sprintf( "bot_join_after_player: %d\n", cv_bot_join_after_player.GetInt() ) ); PrintToConsole( player, str.sprintf( "bot_join_delay: %d\n", bot_join_delay.GetInt() ) ); PrintToConsole( player, str.sprintf( "nextlevel: %s\n", nextlevel.GetString() ) ); int humansInGame = UTIL_HumansInGame( true ); int botsInGame = UTIL_BotsInGame(); PrintToConsole( player, str.sprintf( "%d humans and %d bots in game\n", humansInGame, botsInGame ) ); PrintToConsole( player, str.sprintf( "num CTs (spawnable): %d (%d)\n", m_iNumCT, m_iNumSpawnableCT ) ); PrintToConsole( player, str.sprintf( "num Ts (spawnable): %d (%d)\n", m_iNumTerrorist, m_iNumSpawnableTerrorist ) ); if ( g_fGameOver ) { PrintToConsole( player, str.sprintf( "Game is over!\n" ) ); } PrintToConsole( player, str.sprintf( "\n" ) ); } CON_COMMAND( mp_dump_timers, "Prints round timers to the console for debugging" ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; if ( CSGameRules() ) { CSGameRules()->DumpTimers(); } } // living players on the given team need to be marked as not receiving any money // next round. void CCSGameRules::MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int team) { int playerNum; for (playerNum = 1; playerNum <= gpGlobals->maxClients; ++playerNum) { CCSPlayer *player = (CCSPlayer *)UTIL_PlayerByIndex(playerNum); if (player == NULL) { continue; } if ((player->GetTeamNumber() == team) && (player->IsAlive())) { player->MarkAsNotReceivingMoneyNextRound(); } } } void CCSGameRules::CheckLevelInitialized( void ) { 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_Terrorist = 0; m_iSpawnPointCount_CT = 0; while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL ) { if ( IsSpawnPointValid( ent, NULL ) ) { m_iSpawnPointCount_Terrorist++; } else { Warning("Invalid terrorist spawnpoint at (%.1f,%.1f,%.1f)\n", ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] ); } } while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL ) { if ( IsSpawnPointValid( ent, NULL ) ) { m_iSpawnPointCount_CT++; } else { Warning("Invalid counterterrorist spawnpoint at (%.1f,%.1f,%.1f)\n", ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] ); } } // Is this a logo map? if ( gEntList.FindEntityByClassname( NULL, "info_player_logo" ) ) m_bLogoMap = true; m_bLevelInitialized = true; } } void CCSGameRules::ShowSpawnPoints( void ) { CBaseEntity* ent = NULL; while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL ) { if ( IsSpawnPointValid( ent, NULL ) ) { NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, 600 ); } else { NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, 600); } } while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL ) { if ( IsSpawnPointValid( ent, NULL ) ) { NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, 600 ); } else { NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, 600 ); } } } void CCSGameRules::CheckRestartRound( void ) { // Restart the game if specified by the server int iRestartDelay = mp_restartgame.GetInt(); if ( iRestartDelay > 0 ) { if ( iRestartDelay > 60 ) iRestartDelay = 60; // log the restart UTIL_LogPrintf( "World triggered \"Restart_Round_(%i_%s)\"\n", iRestartDelay, iRestartDelay == 1 ? "second" : "seconds" ); UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", m_iNumCTWins, m_iNumCT ); UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", m_iNumTerroristWins, m_iNumTerrorist ); // let the players know char strRestartDelay[64]; Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay ); UTIL_ClientPrintAll( HUD_PRINTCENTER, "#Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay; m_bCompleteReset = true; mp_restartgame.SetValue( 0 ); } } class SetHumanTeamFunctor { public: SetHumanTeamFunctor( int targetTeam ) { m_targetTeam = targetTeam; m_sourceTeam = ( m_targetTeam == TEAM_CT ) ? TEAM_TERRORIST : TEAM_CT; m_traitors.MakeReliable(); m_loyalists.MakeReliable(); m_loyalists.AddAllPlayers(); } bool operator()( CBasePlayer *basePlayer ) { CCSPlayer *player = ToCSPlayer( basePlayer ); if ( !player ) return true; if ( player->IsBot() ) return true; if ( player->GetTeamNumber() != m_sourceTeam ) return true; if ( player->State_Get() == STATE_PICKINGCLASS ) return true; if ( CSGameRules()->TeamFull( m_targetTeam ) ) return false; if ( CSGameRules()->TeamStacked( m_targetTeam, m_sourceTeam ) ) return false; player->SwitchTeam( m_targetTeam ); m_traitors.AddRecipient( player ); m_loyalists.RemoveRecipient( player ); return true; } void SendNotice( void ) { if ( m_traitors.GetRecipientCount() > 0 ) { UTIL_ClientPrintFilter( m_traitors, HUD_PRINTCENTER, "#Player_Balanced" ); UTIL_ClientPrintFilter( m_loyalists, HUD_PRINTCENTER, "#Teams_Balanced" ); } } private: int m_targetTeam; int m_sourceTeam; CRecipientFilter m_traitors; CRecipientFilter m_loyalists; }; void CCSGameRules::MoveHumansToHumanTeam( void ) { int targetTeam = GetHumanTeam(); if ( targetTeam != TEAM_TERRORIST && targetTeam != TEAM_CT ) return; SetHumanTeamFunctor setTeam( targetTeam ); ForEachPlayer( setTeam ); setTeam.SendNotice(); } void CCSGameRules::BalanceTeams( void ) { int iTeamToSwap = TEAM_UNASSIGNED; int iNumToSwap; if (m_iMapHasVIPSafetyZone == 1) // The ratio for teams is different for Assasination maps { int iDesiredNumCT, iDesiredNumTerrorist; if ( (m_iNumCT + m_iNumTerrorist)%2 != 0) // uneven number of players iDesiredNumCT = (int)((m_iNumCT + m_iNumTerrorist) * 0.55) + 1; else iDesiredNumCT = (int)((m_iNumCT + m_iNumTerrorist)/2); iDesiredNumTerrorist = (m_iNumCT + m_iNumTerrorist) - iDesiredNumCT; if ( m_iNumCT < iDesiredNumCT ) { iTeamToSwap = TEAM_TERRORIST; iNumToSwap = iDesiredNumCT - m_iNumCT; } else if ( m_iNumTerrorist < iDesiredNumTerrorist ) { iTeamToSwap = TEAM_CT; iNumToSwap = iDesiredNumTerrorist - m_iNumTerrorist; } else return; } else { if (m_iNumCT > m_iNumTerrorist) { iTeamToSwap = TEAM_CT; iNumToSwap = (m_iNumCT - m_iNumTerrorist)/2; } else if (m_iNumTerrorist > m_iNumCT) { iTeamToSwap = TEAM_TERRORIST; iNumToSwap = (m_iNumTerrorist - m_iNumCT)/2; } else { return; // Teams are even.. Get out of here. } } if (iNumToSwap > 3) // Don't swap more than 3 players at a time.. This is a naive method of avoiding infinite loops. iNumToSwap = 3; int iTragetTeam = TEAM_UNASSIGNED; if ( iTeamToSwap == TEAM_CT ) { iTragetTeam = TEAM_TERRORIST; } else if ( iTeamToSwap == TEAM_TERRORIST ) { iTragetTeam = TEAM_CT; } else { // no valid team to swap return; } CRecipientFilter traitors; CRecipientFilter loyalists; traitors.MakeReliable(); loyalists.MakeReliable(); loyalists.AddAllPlayers(); for (int i = 0; i < iNumToSwap; i++) { // last person to join the server int iHighestUserID = -1; CCSPlayer *pPlayerToSwap = NULL; // check if target team is full, exit if so if ( TeamFull(iTragetTeam) ) break; // search for player with highest UserID = most recently joined to switch over for ( int j = 1; j <= gpGlobals->maxClients; j++ ) { CCSPlayer *pPlayer = (CCSPlayer *)UTIL_PlayerByIndex( j ); if ( !pPlayer ) continue; CCSBot *bot = dynamic_cast< CCSBot * >(pPlayer); if ( bot ) continue; // don't swap bots - the bot system will handle that if ( pPlayer && ( m_pVIP != pPlayer ) && ( pPlayer->GetTeamNumber() == iTeamToSwap ) && ( engine->GetPlayerUserId( pPlayer->edict() ) > iHighestUserID ) && ( pPlayer->State_Get() != STATE_PICKINGCLASS ) ) { iHighestUserID = engine->GetPlayerUserId( pPlayer->edict() ); pPlayerToSwap = pPlayer; } } if ( pPlayerToSwap != NULL ) { traitors.AddRecipient( pPlayerToSwap ); loyalists.RemoveRecipient( pPlayerToSwap ); pPlayerToSwap->SwitchTeam( iTragetTeam ); } } if ( traitors.GetRecipientCount() > 0 ) { UTIL_ClientPrintFilter( traitors, HUD_PRINTCENTER, "#Player_Balanced" ); UTIL_ClientPrintFilter( loyalists, HUD_PRINTCENTER, "#Teams_Balanced" ); } } bool CCSGameRules::TeamFull( int team_id ) { CheckLevelInitialized(); switch ( team_id ) { case TEAM_TERRORIST: return m_iNumTerrorist >= m_iSpawnPointCount_Terrorist; case TEAM_CT: return m_iNumCT >= m_iSpawnPointCount_CT; } return false; } int CCSGameRules::GetHumanTeam() { if ( FStrEq( "CT", mp_humanteam.GetString() ) ) { return TEAM_CT; } else if ( FStrEq( "T", mp_humanteam.GetString() ) ) { return TEAM_TERRORIST; } return TEAM_UNASSIGNED; } int CCSGameRules::SelectDefaultTeam( bool ignoreBots /*= false*/ ) { if ( ignoreBots && ( FStrEq( cv_bot_join_team.GetString(), "T" ) || FStrEq( cv_bot_join_team.GetString(), "CT" ) ) ) { ignoreBots = false; // don't ignore bots when they can't switch teams } if ( ignoreBots && !mp_autoteambalance.GetBool() ) { ignoreBots = false; // don't ignore bots when they can't switch teams } int team = TEAM_UNASSIGNED; int numTerrorists = m_iNumTerrorist; int numCTs = m_iNumCT; if ( ignoreBots ) { numTerrorists = UTIL_HumansOnTeam( TEAM_TERRORIST ); numCTs = UTIL_HumansOnTeam( TEAM_CT ); } // Choose the team that's lacking players if ( numTerrorists < numCTs ) { team = TEAM_TERRORIST; } else if ( numTerrorists > numCTs ) { team = TEAM_CT; } // Choose the team that's losing else if ( m_iNumTerroristWins < m_iNumCTWins ) { team = TEAM_TERRORIST; } else if ( m_iNumCTWins < m_iNumTerroristWins ) { team = TEAM_CT; } else { // Teams and scores are equal, pick a random team if ( random->RandomInt( 0, 1 ) == 0 ) { team = TEAM_CT; } else { team = TEAM_TERRORIST; } } if ( TeamFull( team ) ) { // Pick the opposite team if ( team == TEAM_TERRORIST ) { team = TEAM_CT; } else { team = TEAM_TERRORIST; } // No choices left if ( TeamFull( team ) ) return TEAM_UNASSIGNED; } return team; } //checks to see if the desired team is stacked, returns true if it is bool CCSGameRules::TeamStacked( int newTeam_id, int curTeam_id ) { //players are allowed to change to their own team if(newTeam_id == curTeam_id) return false; // if mp_limitteams is 0, don't check if ( mp_limitteams.GetInt() == 0 ) return false; switch ( newTeam_id ) { case TEAM_TERRORIST: if(curTeam_id != TEAM_UNASSIGNED && curTeam_id != TEAM_SPECTATOR) { if((m_iNumTerrorist + 1) > (m_iNumCT + mp_limitteams.GetInt() - 1)) return true; else return false; } else { if((m_iNumTerrorist + 1) > (m_iNumCT + mp_limitteams.GetInt())) return true; else return false; } break; case TEAM_CT: if(curTeam_id != TEAM_UNASSIGNED && curTeam_id != TEAM_SPECTATOR) { if((m_iNumCT + 1) > (m_iNumTerrorist + mp_limitteams.GetInt() - 1)) return true; else return false; } else { if((m_iNumCT + 1) > (m_iNumTerrorist + mp_limitteams.GetInt())) return true; else return false; } break; } return false; } //========================================================= //========================================================= bool CCSGameRules::FPlayerCanRespawn( CBasePlayer *pBasePlayer ) { CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer ); if ( !pPlayer ) Error( "FPlayerCanRespawn: pPlayer=0" ); // Player cannot respawn twice in a round if ( pPlayer->m_iNumSpawns > 0 && m_bFirstConnected ) return false; // If they're dead after the map has ended, and it's about to start the next round, // wait for the round restart to respawn them. if ( gpGlobals->curtime < m_flRestartRoundTime ) return false; // Only valid team members can spawn if ( pPlayer->GetTeamNumber() != TEAM_CT && pPlayer->GetTeamNumber() != TEAM_TERRORIST ) return false; // Only players with a valid class can spawn if ( pPlayer->GetClass() == CS_CLASS_NONE ) return false; // Player cannot respawn until next round if more than 20 seconds in // Tabulate the number of players on each team. m_iNumCT = GetGlobalTeam( TEAM_CT )->GetNumPlayers(); m_iNumTerrorist = GetGlobalTeam( TEAM_TERRORIST )->GetNumPlayers(); if ( m_iNumTerrorist > 0 && m_iNumCT > 0 ) { if ( gpGlobals->curtime > (m_fRoundStartTime + 20) ) { //If this player just connected and fadetoblack is on, then maybe //the server admin doesn't want him peeking around. color32_s clr = {0,0,0,255}; if ( mp_fadetoblack.GetBool() ) { UTIL_ScreenFade( pPlayer, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT ); } return false; } } // Player cannot respawn while in the Choose Appearance menu //if ( pPlayer->m_iMenu == Menu_ChooseAppearance ) // return false; return true; } void CCSGameRules::TerminateRound(float tmDelay, int iReason ) { variant_t emptyVariant; int iWinnerTeam = WINNER_NONE; const char *text = "UNKNOWN"; // UTIL_ClientPrintAll( HUD_PRINTCENTER, sentence ); switch ( iReason ) { // Terror wins: case Target_Bombed: text = "#Target_Bombed"; iWinnerTeam = WINNER_TER; break; case VIP_Assassinated: text = "#VIP_Assassinated"; iWinnerTeam = WINNER_TER; break; case Terrorists_Escaped: text = "#Terrorists_Escaped"; iWinnerTeam = WINNER_TER; break; case Terrorists_Win: text = "#Terrorists_Win"; iWinnerTeam = WINNER_TER; break; case Hostages_Not_Rescued: text = "#Hostages_Not_Rescued"; iWinnerTeam = WINNER_TER; break; case VIP_Not_Escaped: text = "#VIP_Not_Escaped"; iWinnerTeam = WINNER_TER; break; // CT wins: case VIP_Escaped: text = "#VIP_Escaped"; iWinnerTeam = WINNER_CT; break; case CTs_PreventEscape: text = "#CTs_PreventEscape"; iWinnerTeam = WINNER_CT; break; case Escaping_Terrorists_Neutralized: text = "#Escaping_Terrorists_Neutralized"; iWinnerTeam = WINNER_CT; break; case Bomb_Defused: text = "#Bomb_Defused"; iWinnerTeam = WINNER_CT; break; case CTs_Win: text = "#CTs_Win"; iWinnerTeam = WINNER_CT; break; case All_Hostages_Rescued: text = "#All_Hostages_Rescued"; iWinnerTeam = WINNER_CT; break; case Target_Saved: text = "#Target_Saved"; iWinnerTeam = WINNER_CT; break; case Terrorists_Not_Escaped: text = "#Terrorists_Not_Escaped"; iWinnerTeam = WINNER_CT; break; // no winners: case Game_Commencing: text = "#Game_Commencing"; iWinnerTeam = WINNER_DRAW; break; case Round_Draw: text = "#Round_Draw"; iWinnerTeam = WINNER_DRAW; break; default: DevMsg("TerminateRound: unknown round end ID %i\n", iReason ); break; } m_iRoundWinStatus = iWinnerTeam; m_flRestartRoundTime = gpGlobals->curtime + tmDelay; if ( iWinnerTeam == WINNER_CT ) { for( int i=0;iAcceptInput( "CTsWin", NULL, NULL, emptyVariant, 0 ); } else if ( iWinnerTeam == WINNER_TER ) { for( int i=0;iAcceptInput( "TerroristsWin", NULL, NULL, emptyVariant, 0 ); } else { Assert( iWinnerTeam == WINNER_NONE || iWinnerTeam == WINNER_DRAW ); } //============================================================================= // HPE_BEGIN: //============================================================================= // [tj] Check for any non-player-specific achievements. ProcessEndOfRoundAchievements(iWinnerTeam, iReason); if( iReason != Game_Commencing ) { // [pfreese] Setup and send win panel event (primarily funfact data) FunFact funfact; funfact.szLocalizationToken = ""; funfact.iPlayer = 0; funfact.iData1 = 0; funfact.iData2 = 0; funfact.iData3 = 0; m_pFunFactManager->GetRoundEndFunFact( iWinnerTeam, iReason, funfact); //Send all the info needed for the win panel IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_round" ); if ( winEvent ) { // determine what categories to send if ( GetRoundRemainingTime() <= 0 ) { // timer expired, defenders win // show total time that was defended winEvent->SetBool( "show_timer_defend", true ); winEvent->SetInt( "timer_time", m_iRoundTime ); } else { // attackers win // show time it took for them to win winEvent->SetBool( "show_timer_attack", true ); int iTimeElapsed = m_iRoundTime - GetRoundRemainingTime(); winEvent->SetInt( "timer_time", iTimeElapsed ); } winEvent->SetInt( "final_event", iReason ); // Set the fun fact data in the event winEvent->SetString( "funfact_token", funfact.szLocalizationToken); winEvent->SetInt( "funfact_player", funfact.iPlayer ); winEvent->SetInt( "funfact_data1", funfact.iData1 ); winEvent->SetInt( "funfact_data2", funfact.iData2 ); winEvent->SetInt( "funfact_data3", funfact.iData3 ); gameeventmanager->FireEvent( winEvent ); } } // [tj] Inform players that the round is over for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if(pPlayer) { pPlayer->OnRoundEnd(iWinnerTeam, iReason); } } //============================================================================= // HPE_END //============================================================================= IGameEvent * event = gameeventmanager->CreateEvent( "round_end" ); if ( event ) { event->SetInt( "winner", iWinnerTeam ); event->SetInt( "reason", iReason ); event->SetString( "message", text ); event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted gameeventmanager->FireEvent( event ); } if ( GetMapRemainingTime() == 0.0f ) { UTIL_LogPrintf("World triggered \"Intermission_Time_Limit\"\n"); GoToIntermission(); } } //============================================================================= // HPE_BEGIN: //============================================================================= // Helper to determine if all players on a team are playing for the same clan static bool IsClanTeam( CTeam *pTeam ) { uint32 iTeamClan = 0; for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) { CBasePlayer *pPlayer = pTeam->GetPlayer( iPlayer ); if ( !pPlayer ) return false; const char *pClanID = engine->GetClientConVarValue( pPlayer->entindex(), "cl_clanid" ); uint32 iPlayerClan = atoi( pClanID ); if ( iPlayer == 0 ) { // Initialize the team clan iTeamClan = iPlayerClan; } else { if ( iPlayerClan != iTeamClan || iPlayerClan == 0 ) return false; } } return iTeamClan != 0; } // [tj] This is where we check non-player-specific that occur at the end of the round void CCSGameRules::ProcessEndOfRoundAchievements(int iWinnerTeam, int iReason) { if (iWinnerTeam == WINNER_CT || iWinnerTeam == WINNER_TER) { int losingTeamId = (iWinnerTeam == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT; CTeam* losingTeam = GetGlobalTeam(losingTeamId); //Check for players we should ignore when checking team size. int ignoreCount = 0; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); if (pPlayer) { int teamNum = pPlayer->GetTeamNumber(); if ( teamNum == losingTeamId ) { if (pPlayer->WasNotKilledNaturally()) { ignoreCount++; } } } } // [tj] Check extermination with no losses achievement if ( ( ( iReason == CTs_Win && m_bNoCTsKilled ) || ( iReason == Terrorists_Win && m_bNoTerroristsKilled ) ) && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement) { CTeam *pTeam = GetGlobalTeam( iWinnerTeam ); for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) { CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); Assert( pPlayer ); if ( !pPlayer ) continue; pPlayer->AwardAchievement(CSLosslessExtermination); } } // [tj] Check flawless victory achievement - currently requiring extermination if (((iReason == CTs_Win && m_bNoCTsDamaged) || (iReason == Terrorists_Win && m_bNoTerroristsDamaged)) && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement) { CTeam *pTeam = GetGlobalTeam( iWinnerTeam ); for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) { CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); Assert( pPlayer ); if ( !pPlayer ) continue; pPlayer->AwardAchievement(CSFlawlessVictory); } } // [tj] Check bloodless victory achievement if (((iWinnerTeam == TEAM_TERRORIST && m_bNoCTsKilled) || (iWinnerTeam == Terrorists_Win && m_bNoTerroristsKilled)) && losingTeam && losingTeam->GetNumPlayers() >= AchievementConsts::DefaultMinOpponentsForAchievement) { CTeam *pTeam = GetGlobalTeam( iWinnerTeam ); for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) { CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); Assert( pPlayer ); if ( !pPlayer ) continue; pPlayer->AwardAchievement(CSBloodlessVictory); } } // Check the clan match achievement CTeam *pWinningTeam = GetGlobalTeam( iWinnerTeam ); if ( pWinningTeam && pWinningTeam->GetNumPlayers() >= AchievementConsts::DefaultMinOpponentsForAchievement && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement && IsClanTeam( pWinningTeam ) && IsClanTeam( losingTeam ) ) { for ( int iPlayer=0; iPlayer < pWinningTeam->GetNumPlayers(); iPlayer++ ) { CCSPlayer *pPlayer = ToCSPlayer( pWinningTeam->GetPlayer( iPlayer ) ); if ( !pPlayer ) continue; pPlayer->AwardAchievement( CSWinClanMatch ); } } } } //[tj] Counts the number of players in each category in the struct (dead, alive, etc...) void CCSGameRules::GetPlayerCounts(TeamPlayerCounts teamCounts[TEAM_MAXCOUNT]) { memset(teamCounts, 0, sizeof(TeamPlayerCounts) * TEAM_MAXCOUNT); for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); if (pPlayer) { int iTeam = pPlayer->GetTeamNumber(); if (iTeam >= 0 && iTeam < TEAM_MAXCOUNT) { ++teamCounts[iTeam].totalPlayers; if (pPlayer->IsAlive()) { ++teamCounts[iTeam].totalAlivePlayers; } else { ++teamCounts[iTeam].totalDeadPlayers; //If the player has joined a team bit isn't in the game yet if (pPlayer->State_Get() == STATE_PICKINGCLASS) { ++teamCounts[iTeam].unenteredPlayers; } else if (pPlayer->WasNotKilledNaturally()) { ++teamCounts[iTeam].suicidedPlayers; } else { ++teamCounts[iTeam].killedPlayers; } } } } } } //============================================================================= // HPE_END //============================================================================= void CCSGameRules::UpdateTeamScores() { CTeam *pTerrorists = GetGlobalTeam( TEAM_TERRORIST ); CTeam *pCTs = GetGlobalTeam( TEAM_CT ); Assert( pTerrorists && pCTs ); if( pTerrorists ) pTerrorists->SetScore( m_iNumTerroristWins ); if( pCTs ) pCTs->SetScore( m_iNumCTWins ); } void CCSGameRules::CheckMapConditions() { // Check to see if this map has a bomb target in it if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) ) { m_bMapHasBombTarget = true; m_bMapHasBombZone = true; } else if ( gEntList.FindEntityByClassname( NULL, "info_bomb_target" ) ) { m_bMapHasBombTarget = true; m_bMapHasBombZone = false; } else { m_bMapHasBombTarget = false; m_bMapHasBombZone = false; } // See if the map has func_buyzone entities // Used by CBasePlayer::HandleSignals() to support maps without these entities if ( gEntList.FindEntityByClassname( NULL, "func_buyzone" ) ) { m_bMapHasBuyZone = true; } else { m_bMapHasBuyZone = false; } // Check to see if this map has hostage rescue zones if ( gEntList.FindEntityByClassname( NULL, "func_hostage_rescue" ) ) { m_bMapHasRescueZone = true; } else { m_bMapHasRescueZone = false; } // GOOSEMAN : See if this map has func_escapezone entities if ( gEntList.FindEntityByClassname( NULL, "func_escapezone" ) ) { m_bMapHasEscapeZone = true; } else { m_bMapHasEscapeZone = false; } // Check to see if this map has VIP safety zones if ( gEntList.FindEntityByClassname( NULL, "func_vip_safetyzone" ) ) { m_iMapHasVIPSafetyZone = 1; } else { m_iMapHasVIPSafetyZone = 2; } } void CCSGameRules::SwapAllPlayers() { // MOTODO we have to make sure that enought spaning points exits Assert ( 0 ); for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { /* CCSPlayer *pPlayer = CCSPlayer::Instance( i ); if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) pPlayer->SwitchTeam(); */ } // Swap Team victories int iTemp; iTemp = m_iNumCTWins; m_iNumCTWins = m_iNumTerroristWins; m_iNumTerroristWins = iTemp; // Update the clients team score UpdateTeamScores(); } bool CS_FindInList( const char **pStrings, const char *pToFind ) { return FindInList( pStrings, pToFind ); } void CCSGameRules::CleanUpMap() { if (IsLogoMap()) return; // Recreate all the map entities from the map data (preserving their indices), // then remove everything else except the players. // Get rid of all entities except players. CBaseEntity *pCur = gEntList.FirstEnt(); while ( pCur ) { CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pCur ); // Weapons with owners don't want to be removed.. if ( pWeapon ) { //============================================================================= // HPE_BEGIN: // [dwenger] Handle round restart processing for the weapon. //============================================================================= pWeapon->OnRoundRestart(); //============================================================================= // HPE_END //============================================================================= if ( pWeapon->ShouldRemoveOnRoundRestart() ) { UTIL_Remove( pCur ); } } // remove entities that has to be restored on roundrestart (breakables etc) else if ( !CS_FindInList( s_PreserveEnts, pCur->GetClassname() ) ) { UTIL_Remove( pCur ); } pCur = gEntList.NextEnt( pCur ); } // Really remove the entities so we can have access to their slots below. gEntList.CleanupDeleteList(); // Cancel all queued events, in case a func_bomb_target fired some delayed outputs that // could kill respawning CTs g_EventQueue.Clear(); // Now reload the map entities. class CCSMapEntityFilter : public IMapEntityFilter { public: virtual bool ShouldCreateEntity( const char *pClassname ) { // Don't recreate the preserved entities. if ( !CS_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 // CCSMapLoadEntityFilter, 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. }; CCSMapEntityFilter filter; filter.m_iIterator = g_MapEntityRefs.Head(); // DO NOT CALL SPAWN ON info_node ENTITIES! MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); } bool CCSGameRules::IsThereABomber() { for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = CCSPlayer::Instance( i ); if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) { if ( pPlayer->GetTeamNumber() == TEAM_CT ) continue; if ( pPlayer->HasC4() ) return true; //There you are. } } //Didn't find a bomber. return false; } void CCSGameRules::EndRound() { // fake a round end CSGameRules()->TerminateRound( 0.0f, Round_Draw ); } CBaseEntity *CCSGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) { // gat valid spwan 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( &pSpawnSpot->GetAbsOrigin(), &pSpawnSpot->GetLocalAngles(), &vec3_origin ); pPlayer->m_Local.m_vecPunchAngle = vec3_angle; return pSpawnSpot; } // checks if the spot is clear of players bool CCSGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer ) { if ( !pSpot->IsTriggered( pPlayer ) ) { 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 ); } bool CCSGameRules::IsThereABomb() { bool bBombFound = false; /* are there any bombs, either laying around, or in someone's inventory? */ if( gEntList.FindEntityByClassname( NULL, WEAPON_C4_CLASSNAME ) != 0 ) { bBombFound = true; } /* what about planted bombs!? */ else if( gEntList.FindEntityByClassname( NULL, PLANTED_C4_CLASSNAME ) != 0 ) { bBombFound = true; } return bBombFound; } void CCSGameRules::HostageTouched() { if( gpGlobals->curtime > m_flNextHostageAnnouncement && m_iRoundWinStatus == WINNER_NONE ) { //BroadcastSound( "Event.HostageTouched" ); m_flNextHostageAnnouncement = gpGlobals->curtime + 60.0; } } void CCSGameRules::CreateStandardEntities() { // Create the player resource g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "cs_player_manager", vec3_origin, vec3_angle ); // Create the entity that will send our data to the client. #ifdef DBGFLAG_ASSERT CBaseEntity *pEnt = #endif CBaseEntity::Create( "cs_gamerules", vec3_origin, vec3_angle ); Assert( pEnt ); } #define MY_USHRT_MAX 0xffff #define MY_UCHAR_MAX 0xff bool DataHasChanged( void ) { for ( int i = 0; i < CS_NUM_LEVELS; i++ ) { if ( g_iTerroristVictories[i] != 0 || g_iCounterTVictories[i] != 0 ) return true; } for ( int i = 0; i < WEAPON_MAX; i++ ) { if ( g_iWeaponPurchases[i] != 0 ) return true; } return false; } void CCSGameRules::UploadGameStats( void ) { g_flGameStatsUpdateTime -= gpGlobals->curtime; if ( g_flGameStatsUpdateTime > 0 ) return; if ( IsBlackMarket() == false ) return; if ( m_bDontUploadStats == true ) return; if ( DataHasChanged() == true ) { cs_gamestats_t stats; memset( &stats, 0, sizeof(stats) ); // Header stats.header.iVersion = CS_STATS_BLOB_VERSION; Q_strncpy( stats.header.szGameName, "cstrike", 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 ) & MY_UCHAR_MAX; stats.header.ipAddr[2] = ( ip >> 8 ) & MY_UCHAR_MAX; stats.header.ipAddr[3] = ( ip ) & MY_UCHAR_MAX; } 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 ); memcpy( stats.iTerroristVictories, g_iTerroristVictories, sizeof( g_iTerroristVictories) ); memcpy( stats.iCounterTVictories, g_iCounterTVictories, sizeof( g_iCounterTVictories) ); memcpy( stats.iBlackMarketPurchases, g_iWeaponPurchases, sizeof( g_iWeaponPurchases) ); stats.iAutoBuyPurchases = g_iAutoBuyPurchases; stats.iReBuyPurchases = g_iReBuyPurchases; stats.iAutoBuyM4A1Purchases = g_iAutoBuyM4A1Purchases; stats.iAutoBuyAK47Purchases = g_iAutoBuyAK47Purchases; stats.iAutoBuyFamasPurchases = g_iAutoBuyFamasPurchases; stats.iAutoBuyGalilPurchases = g_iAutoBuyGalilPurchases; stats.iAutoBuyVestHelmPurchases = g_iAutoBuyVestHelmPurchases; stats.iAutoBuyVestPurchases = g_iAutoBuyVestPurchases; const void *pvBlobData = ( const void * )( &stats ); unsigned int uBlobSize = sizeof( stats ); if ( gamestatsuploader ) { gamestatsuploader->UploadGameStats( STRING( gpGlobals->mapname ), CS_STATS_BLOB_VERSION, uBlobSize, pvBlobData ); } memset( g_iWeaponPurchases, 0, sizeof( g_iWeaponPurchases) ); memset( g_iTerroristVictories, 0, sizeof( g_iTerroristVictories) ); memset( g_iCounterTVictories, 0, sizeof( g_iTerroristVictories) ); g_iAutoBuyPurchases = 0; g_iReBuyPurchases = 0; g_iAutoBuyM4A1Purchases = 0; g_iAutoBuyAK47Purchases = 0; g_iAutoBuyFamasPurchases = 0; g_iAutoBuyGalilPurchases = 0; g_iAutoBuyVestHelmPurchases = 0; g_iAutoBuyVestPurchases = 0; } g_flGameStatsUpdateTime = CS_GAME_STATS_UPDATE; //Next update is between 22 and 24 hours. } #endif // CLIENT_DLL CBaseCombatWeapon *CCSGameRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) { CBaseCombatWeapon *bestWeapon = NULL; // search all the weapons looking for the closest next for ( int i = 0; i < MAX_WEAPONS; i++ ) { CBaseCombatWeapon *weapon = pPlayer->GetWeapon(i); if ( !weapon ) continue; if ( !weapon->CanBeSelected() || weapon == pCurrentWeapon ) continue; #ifndef CLIENT_DLL CCSPlayer *csPlayer = ToCSPlayer(pPlayer); CWeaponCSBase *csWeapon = static_cast< CWeaponCSBase * >(weapon); if ( csPlayer && csPlayer->IsBot() && !TheCSBots()->IsWeaponUseable( csWeapon ) ) continue; #endif // CLIENT_DLL if ( bestWeapon ) { if ( weapon->GetSlot() < bestWeapon->GetSlot() ) { bestWeapon = weapon; } else if ( weapon->GetSlot() == bestWeapon->GetSlot() && weapon->GetPosition() < bestWeapon->GetPosition() ) { bestWeapon = weapon; } } else { bestWeapon = weapon; } } return bestWeapon; } float CCSGameRules::GetMapRemainingTime() { #ifdef GAME_DLL if ( nextlevel.GetString() && *nextlevel.GetString() ) { return 0; } #endif // if timelimit is disabled, return -1 if ( mp_timelimit.GetInt() <= 0 ) return -1; // timelimit is in minutes float flTimeLeft = ( m_flGameStartTime + mp_timelimit.GetInt() * 60 ) - gpGlobals->curtime; // never return a negative value if ( flTimeLeft < 0 ) flTimeLeft = 0; return flTimeLeft; } float CCSGameRules::GetMapElapsedTime( void ) { return gpGlobals->curtime; } float CCSGameRules::GetRoundRemainingTime() { return (float) (m_fRoundStartTime + m_iRoundTime) - gpGlobals->curtime; } float CCSGameRules::GetRoundStartTime() { return m_fRoundStartTime; } float CCSGameRules::GetRoundElapsedTime() { return gpGlobals->curtime - m_fRoundStartTime; } bool CCSGameRules::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; } // TODO: make a CS-SPECIFIC COLLISION GROUP FOR PHYSICS PROPS THAT USE THIS COLLISION BEHAVIOR. if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) { return false; } if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) { // let debris and multiplayer objects collide return true; } return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); } bool CCSGameRules::IsFreezePeriod() { return m_bFreezePeriod; } bool CCSGameRules::IsVIPMap() const { //MIKETODO: VIP mode return false; } bool CCSGameRules::IsBombDefuseMap() const { return m_bMapHasBombTarget; } bool CCSGameRules::IsHostageRescueMap() const { return m_bMapHasRescueZone; } bool CCSGameRules::IsLogoMap() const { return m_bLogoMap; } float CCSGameRules::GetBuyTimeLength() const { return ( mp_buytime.GetFloat() * 60 ); } bool CCSGameRules::IsBuyTimeElapsed() { return ( GetRoundElapsedTime() > GetBuyTimeLength() ); } int CCSGameRules::DefaultFOV() { return 90; } const CViewVectors* CCSGameRules::GetViewVectors() const { return &g_CSViewVectors; } //----------------------------------------------------------------------------- // 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) static CCSAmmoDef ammoDef; CCSAmmoDef* GetCSAmmoDef() { GetAmmoDef(); // to initialize the ammo info return &ammoDef; } CAmmoDef* GetAmmoDef() { static bool bInitted = false; if ( !bInitted ) { bInitted = true; ammoDef.AddAmmoType( BULLET_PLAYER_50AE, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_50AE_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); ammoDef.AddAmmoType( BULLET_PLAYER_762MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_762mm_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); ammoDef.AddAmmoType( BULLET_PLAYER_556MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); ammoDef.AddAmmoType( BULLET_PLAYER_556MM_BOX, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_box_max",2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); ammoDef.AddAmmoType( BULLET_PLAYER_338MAG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_338mag_max", 2800 * BULLET_IMPULSE_EXAGGERATION, 0, 12, 16 ); ammoDef.AddAmmoType( BULLET_PLAYER_9MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_9mm_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 5, 10 ); ammoDef.AddAmmoType( BULLET_PLAYER_BUCKSHOT, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_buckshot_max", 600 * BULLET_IMPULSE_EXAGGERATION, 0, 3, 6 ); ammoDef.AddAmmoType( BULLET_PLAYER_45ACP, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_45acp_max", 2100 * BULLET_IMPULSE_EXAGGERATION, 0, 6, 10 ); ammoDef.AddAmmoType( BULLET_PLAYER_357SIG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 4, 8 ); ammoDef.AddAmmoType( BULLET_PLAYER_57MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_57mm_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 4, 8 ); ammoDef.AddAmmoType( AMMO_TYPE_HEGRENADE, DMG_BLAST, TRACER_LINE, 0, 0, "ammo_hegrenade_max", 1, 0 ); ammoDef.AddAmmoType( AMMO_TYPE_FLASHBANG, 0, TRACER_LINE, 0, 0, "ammo_flashbang_max", 1, 0 ); ammoDef.AddAmmoType( AMMO_TYPE_SMOKEGRENADE, 0, TRACER_LINE, 0, 0, "ammo_smokegrenade_max", 1, 0 ); //Adrian: I set all the prices to 0 just so the rest of the buy code works //This should be revisited. ammoDef.AddAmmoCost( BULLET_PLAYER_50AE, 0, 7 ); ammoDef.AddAmmoCost( BULLET_PLAYER_762MM, 0, 30 ); ammoDef.AddAmmoCost( BULLET_PLAYER_556MM, 0, 30 ); ammoDef.AddAmmoCost( BULLET_PLAYER_556MM_BOX, 0, 30 ); ammoDef.AddAmmoCost( BULLET_PLAYER_338MAG, 0, 10 ); ammoDef.AddAmmoCost( BULLET_PLAYER_9MM, 0, 30 ); ammoDef.AddAmmoCost( BULLET_PLAYER_BUCKSHOT, 0, 8 ); ammoDef.AddAmmoCost( BULLET_PLAYER_45ACP, 0, 25 ); ammoDef.AddAmmoCost( BULLET_PLAYER_357SIG, 0, 13 ); ammoDef.AddAmmoCost( BULLET_PLAYER_57MM, 0, 50 ); } return &ammoDef; } #ifndef CLIENT_DLL const char *CCSGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer ) { const char *pszPrefix = NULL; if ( !pPlayer ) // dedicated server output { pszPrefix = ""; } else { // team only if ( bTeamOnly == TRUE ) { if ( pPlayer->GetTeamNumber() == TEAM_CT ) { if ( pPlayer->m_lifeState == LIFE_ALIVE ) { pszPrefix = "(Counter-Terrorist)"; } else { pszPrefix = "*DEAD*(Counter-Terrorist)"; } } else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) { if ( pPlayer->m_lifeState == LIFE_ALIVE ) { pszPrefix = "(Terrorist)"; } else { pszPrefix = "*DEAD*(Terrorist)"; } } else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) { pszPrefix = "(Spectator)"; } } // everyone else { if ( pPlayer->m_lifeState == LIFE_ALIVE ) { pszPrefix = ""; } else { if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) { pszPrefix = "*DEAD*"; } else { pszPrefix = "*SPEC*"; } } } } return pszPrefix; } const char *CCSGameRules::GetChatLocation( bool bTeamOnly, CBasePlayer *pPlayer ) { if ( !pPlayer ) // dedicated server output { return NULL; } // only teammates see locations if ( !bTeamOnly ) return NULL; // only living players have locations if ( pPlayer->GetTeamNumber() != TEAM_CT && pPlayer->GetTeamNumber() != TEAM_TERRORIST ) return NULL; if ( !pPlayer->IsAlive() ) return NULL; return pPlayer->GetLastKnownPlaceName(); } const char *CCSGameRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ) { if ( !pPlayer ) // dedicated server output { return NULL; } const char *pszFormat = NULL; // team only if ( bTeamOnly == TRUE ) { if ( pPlayer->GetTeamNumber() == TEAM_CT ) { if ( pPlayer->m_lifeState == LIFE_ALIVE ) { const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer ); if ( chatLocation && *chatLocation ) { pszFormat = "Cstrike_Chat_CT_Loc"; } else { pszFormat = "Cstrike_Chat_CT"; } } else { pszFormat = "Cstrike_Chat_CT_Dead"; } } else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) { if ( pPlayer->m_lifeState == LIFE_ALIVE ) { const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer ); if ( chatLocation && *chatLocation ) { pszFormat = "Cstrike_Chat_T_Loc"; } else { pszFormat = "Cstrike_Chat_T"; } } else { pszFormat = "Cstrike_Chat_T_Dead"; } } else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) { pszFormat = "Cstrike_Chat_Spec"; } } // everyone else { if ( pPlayer->m_lifeState == LIFE_ALIVE ) { pszFormat = "Cstrike_Chat_All"; } else { if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) { pszFormat = "Cstrike_Chat_AllDead"; } else { pszFormat = "Cstrike_Chat_AllSpec"; } } } return pszFormat; } void CCSGameRules::ClientSettingsChanged( CBasePlayer *pPlayer ) { const char *pszNewName = engine->GetClientConVarValue( pPlayer->entindex(), "name" ); const char *pszOldName = pPlayer->GetPlayerName(); CCSPlayer *pCSPlayer = (CCSPlayer*)pPlayer; if ( pszOldName[0] != 0 && Q_strncmp( pszOldName, pszNewName, MAX_PLAYER_NAME_LENGTH-1 ) ) { pCSPlayer->ChangeName( pszNewName ); } pCSPlayer->m_bShowHints = true; if ( pCSPlayer->IsNetClient() ) { const char *pShowHints = engine->GetClientConVarValue( engine->IndexOfEdict( pCSPlayer->edict() ), "cl_autohelp" ); if ( pShowHints && atoi( pShowHints ) <= 0 ) { pCSPlayer->m_bShowHints = false; } } } bool CCSGameRules::FAllowNPCs( void ) { return false; } bool CCSGameRules::IsFriendlyFireOn( void ) { return friendlyfire.GetBool(); } CON_COMMAND( map_showspawnpoints, "Shows player spawn points (red=invalid)" ) { CSGameRules()->ShowSpawnPoints(); } void DrawSphere( const Vector& pos, float radius, int r, int g, int b, float lifetime ) { Vector edge, lastEdge; NDebugOverlay::Line( pos, pos + Vector( 0, 0, 50 ), r, g, b, true, lifetime ); lastEdge = Vector( radius + pos.x, pos.y, pos.z ); float angle; for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) { edge.x = radius * BotCOS( angle ) + pos.x; edge.y = pos.y; edge.z = radius * BotSIN( angle ) + pos.z; NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime ); lastEdge = edge; } lastEdge = Vector( pos.x, radius + pos.y, pos.z ); for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) { edge.x = pos.x; edge.y = radius * BotCOS( angle ) + pos.y; edge.z = radius * BotSIN( angle ) + pos.z; NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime ); lastEdge = edge; } lastEdge = Vector( pos.x, radius + pos.y, pos.z ); for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) { edge.x = radius * BotCOS( angle ) + pos.x; edge.y = radius * BotSIN( angle ) + pos.y; edge.z = pos.z; NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime ); lastEdge = edge; } } CON_COMMAND_F( map_showbombradius, "Shows bomb radius from the center of each bomb site and planted bomb.", FCVAR_CHEAT ) { float flBombDamage = 500.0f; if ( g_pMapInfo ) flBombDamage = g_pMapInfo->m_flBombRadius; float flBombRadius = flBombDamage * 3.5f; Msg( "Bomb Damage is %.0f, Radius is %.0f\n", flBombDamage, flBombRadius ); CBaseEntity* ent = NULL; while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL ) { const Vector &pos = ent->WorldSpaceCenter(); DrawSphere( pos, flBombRadius, 255, 255, 0, 10 ); } ent = NULL; while ( ( ent = gEntList.FindEntityByClassname( ent, "planted_c4" ) ) != NULL ) { const Vector &pos = ent->WorldSpaceCenter(); DrawSphere( pos, flBombRadius, 255, 0, 0, 10 ); } } CON_COMMAND_F( map_setbombradius, "Sets the bomb radius for the map.", FCVAR_CHEAT ) { if ( args.ArgC() != 2 ) return; if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; if ( !g_pMapInfo ) CBaseEntity::Create( "info_map_parameters", vec3_origin, vec3_angle ); if ( !g_pMapInfo ) return; g_pMapInfo->m_flBombRadius = atof( args[1] ); map_showbombradius( args ); } void CreateBlackMarketString( void ) { g_StringTableBlackMarket = networkstringtable->CreateStringTable( "BlackMarketTable" , 1 ); } int CCSGameRules::GetStartMoney( void ) { if ( IsBlackMarket() ) { return atoi( mp_startmoney.GetDefault() ); } return mp_startmoney.GetInt(); } //============================================================================= // HPE_BEGIN: // [menglish] Set up anything for all players that changes based on new players spawning mid-game // Find and return fun fact data //============================================================================= //----------------------------------------------------------------------------- // Purpose: Called when a player joins the game after it's started yet can still spawn in //----------------------------------------------------------------------------- void CCSGameRules::SpawningLatePlayer( CCSPlayer* pLatePlayer ) { //Reset the round kills number of enemies for the opposite team for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); if(pPlayer) { if(pPlayer->GetTeamNumber() == pLatePlayer->GetTeamNumber()) { continue; } pPlayer->m_NumEnemiesAtRoundStart++; } } } //============================================================================= // HPE_END //============================================================================= //============================================================================= // HPE_BEGIN: // [pfreese] Test for "pistol" round, defined as the default starting round // when players cannot purchase anything primary weapons //============================================================================= bool CCSGameRules::IsPistolRound() { return m_iTotalRoundsPlayed == 0 && GetStartMoney() <= 800; } //============================================================================= // HPE_END //============================================================================= //============================================================================= // HPE_BEGIN: // [tj] So game rules can react to damage taken // [menglish] //============================================================================= void CCSGameRules::PlayerTookDamage(CCSPlayer* player, const CTakeDamageInfo &damageInfo) { CBaseEntity *pInflictor = damageInfo.GetInflictor(); CBaseEntity *pAttacker = damageInfo.GetAttacker(); CCSPlayer *pCSScorer = (CCSPlayer *)(GetDeathScorer( pAttacker, pInflictor )); if ( player && pCSScorer ) { if (player->GetTeamNumber() == TEAM_CT) { m_bNoCTsDamaged = false; } if (player->GetTeamNumber() == TEAM_TERRORIST) { m_bNoTerroristsDamaged = false; } // set the first blood if this is the first and the victim is on a different team then the player if ( m_pFirstBlood == NULL && pCSScorer != player && pCSScorer->GetTeamNumber() != player ->GetTeamNumber() ) { m_pFirstBlood = pCSScorer; m_firstBloodTime = gpGlobals->curtime - m_fRoundStartTime; } } } //============================================================================= // HPE_END //============================================================================= #endif bool CCSGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ) { #ifdef GAME_DLL if( pPlayer ) { int iPlayerTeam = pPlayer->GetTeamNumber(); if( ( iPlayerTeam == TEAM_CT ) || ( iPlayerTeam == TEAM_TERRORIST ) ) return false; } #else int iLocalPlayerTeam = GetLocalPlayerTeam(); if( ( iLocalPlayerTeam == TEAM_CT ) || ( iLocalPlayerTeam == TEAM_TERRORIST ) ) return false; #endif return true; } #ifdef GAME_DLL struct convar_tags_t { const char *pszConVar; const char *pszTag; }; // The list of convars that automatically turn on tags when they're changed. // Convars in this list need to have the FCVAR_NOTIFY flag set on them, so the // tags are recalculated and uploaded to the master server when the convar is changed. convar_tags_t convars_to_check_for_tags[] = { { "mp_friendlyfire", "friendlyfire" }, { "bot_quota", "bots" }, { "sv_nostats", "nostats" }, { "mp_startmoney", "startmoney" }, { "sv_allowminmodels", "nominmodels" }, { "sv_enablebunnyhopping", "bunnyhopping" }, { "sv_competitive_minspec", "compspec" }, { "mp_holiday_nogifts", "nogifts" }, }; //----------------------------------------------------------------------------- // Purpose: Engine asks for the list of convars that should tag the server //----------------------------------------------------------------------------- void CCSGameRules::GetTaggedConVarList( KeyValues *pCvarTagList ) { BaseClass::GetTaggedConVarList( pCvarTagList ); for ( int i = 0; i < ARRAYSIZE(convars_to_check_for_tags); i++ ) { KeyValues *pKV = new KeyValues( "tag" ); pKV->SetString( "convar", convars_to_check_for_tags[i].pszConVar ); pKV->SetString( "tag", convars_to_check_for_tags[i].pszTag ); pCvarTagList->AddSubKey( pKV ); } } #endif int CCSGameRules::GetBlackMarketPriceForWeapon( int iWeaponID ) { if ( m_pPrices == NULL ) { GetBlackMarketPriceList(); } if ( m_pPrices ) return m_pPrices->iCurrentPrice[iWeaponID]; else return 0; } int CCSGameRules::GetBlackMarketPreviousPriceForWeapon( int iWeaponID ) { if ( m_pPrices == NULL ) { GetBlackMarketPriceList(); } if ( m_pPrices ) return m_pPrices->iPreviousPrice[iWeaponID]; else return 0; } const weeklyprice_t *CCSGameRules::GetBlackMarketPriceList( void ) { if ( m_StringTableBlackMarket == NULL ) { m_StringTableBlackMarket = networkstringtable->FindTable( CS_GAMERULES_BLACKMARKET_TABLE_NAME); } if ( m_pPrices == NULL ) { int iSize = 0; INetworkStringTable *pTable = m_StringTableBlackMarket; if ( pTable && pTable->GetNumStrings() > 0 ) { m_pPrices = (const weeklyprice_t *)pTable->GetStringUserData( 0, &iSize ); } } if ( m_pPrices ) { PrepareEquipmentInfo(); } return m_pPrices; } void CCSGameRules::SetBlackMarketPrices( bool bSetDefaults ) { for ( int i = 1; i < WEAPON_MAX; i++ ) { if ( i == WEAPON_SHIELDGUN ) continue; CCSWeaponInfo *info = GetWeaponInfo( (CSWeaponID)i ); if ( info == NULL ) continue; if ( bSetDefaults == false ) { info->SetWeaponPrice( GetBlackMarketPriceForWeapon( i ) ); info->SetPreviousPrice( GetBlackMarketPreviousPriceForWeapon( i ) ); } else { info->SetWeaponPrice( info->GetDefaultPrice() ); } } } #ifdef CLIENT_DLL CCSGameRules::CCSGameRules() { CSGameRules()->m_StringTableBlackMarket = NULL; m_pPrices = NULL; m_bBlackMarket = false; } void TestTable( void ) { CSGameRules()->m_StringTableBlackMarket = networkstringtable->FindTable( CS_GAMERULES_BLACKMARKET_TABLE_NAME); if ( CSGameRules()->m_StringTableBlackMarket == NULL ) return; int iIndex = CSGameRules()->m_StringTableBlackMarket->FindStringIndex( "blackmarket_prices" ); int iSize = 0; const weeklyprice_t *pPrices = NULL; pPrices = (const weeklyprice_t *)(CSGameRules()->m_StringTableBlackMarket)->GetStringUserData( iIndex, &iSize ); } #ifdef DEBUG ConCommand cs_testtable( "cs_testtable", TestTable ); #endif //----------------------------------------------------------------------------- // Enforce certain values on the specified convar. //----------------------------------------------------------------------------- void EnforceCompetitiveCVar( const char *szCvarName, float fMinValue, float fMaxValue = FLT_MAX, int iArgs = 0, ... ) { // Doing this check first because OK values might be outside the min/max range ConVarRef competitiveConvar(szCvarName); float fValue = competitiveConvar.GetFloat(); va_list vl; va_start(vl, iArgs); for( int i=0; i< iArgs; ++i ) { if( (int)fValue == (int)va_arg(vl,double) ) return; } va_end(vl); if( fValue < fMinValue || fValue > fMaxValue ) { float fNewValue = MAX( MIN( fValue, fMaxValue ), fMinValue ); competitiveConvar.SetValue( fNewValue ); DevMsg( "Convar %s enforced by server (see sv_competitive_minspec.) Set to %2f.\n", szCvarName, fNewValue ); } } //----------------------------------------------------------------------------- // An interface used by ENABLE_COMPETITIVE_CONVAR macro that lets the classes // defined in the macro to be stored and acted on. //----------------------------------------------------------------------------- class ICompetitiveConvar { public: // It is a best practice to always have a virtual destructor in an interface // class. Otherwise if the derived classes have destructors they will not be // called. virtual ~ICompetitiveConvar() {} virtual void BackupConvar() = 0; virtual void EnforceRestrictions() = 0; virtual void RestoreOriginalValue() = 0; virtual void InstallChangeCallback() = 0; }; //----------------------------------------------------------------------------- // A manager for all enforced competitive convars. //----------------------------------------------------------------------------- class CCompetitiveCvarManager : public CAutoGameSystem { public: typedef CUtlVector CompetitiveConvarList_t; static void AddConvarToList( ICompetitiveConvar* pCVar ) { GetConvarList()->AddToTail( pCVar ); } static void BackupAllConvars() { FOR_EACH_VEC( *GetConvarList(), i ) { (*GetConvarList())[i]->BackupConvar(); } } static void EnforceRestrictionsOnAllConvars() { FOR_EACH_VEC( *GetConvarList(), i ) { (*GetConvarList())[i]->EnforceRestrictions(); } } static void RestoreAllOriginalValues() { FOR_EACH_VEC( *GetConvarList(), i ) { (*GetConvarList())[i]->RestoreOriginalValue(); } } static CompetitiveConvarList_t* GetConvarList() { if( !s_pCompetitiveConvars ) { s_pCompetitiveConvars = new CompetitiveConvarList_t(); } return s_pCompetitiveConvars; } static KeyValues* GetConVarBackupKV() { if( !s_pConVarBackups ) { s_pConVarBackups = new KeyValues("ConVarBackups"); } return s_pConVarBackups; } virtual bool Init() { FOR_EACH_VEC( *GetConvarList(), i ) { (*GetConvarList())[i]->InstallChangeCallback(); } return true; } virtual void Shutdown() { FOR_EACH_VEC( *GetConvarList(), i ) { delete (*GetConvarList())[i]; } delete s_pCompetitiveConvars; s_pCompetitiveConvars = null; s_pConVarBackups->deleteThis(); s_pConVarBackups = null; } private: static CompetitiveConvarList_t* s_pCompetitiveConvars; static KeyValues* s_pConVarBackups; }; static CCompetitiveCvarManager *s_pCompetitiveCvarManager = new CCompetitiveCvarManager(); CCompetitiveCvarManager::CompetitiveConvarList_t* CCompetitiveCvarManager::s_pCompetitiveConvars = null; KeyValues* CCompetitiveCvarManager::s_pConVarBackups = null; //----------------------------------------------------------------------------- // Macro to define restrictions on convars with "sv_competitive_minspec 1" // Usage: ENABLE_COMPETITIVE_CONVAR( convarName, minValue, maxValue, optionalValues, opVal1, opVal2, ... //----------------------------------------------------------------------------- #define ENABLE_COMPETITIVE_CONVAR( convarName, ... ) \ class CCompetitiveMinspecConvar##convarName : public ICompetitiveConvar { \ public: \ CCompetitiveMinspecConvar##convarName(){ CCompetitiveCvarManager::AddConvarToList(this);} \ static void on_changed_##convarName( IConVar *var, const char *pOldValue, float flOldValue ){ \ if( sv_competitive_minspec.GetBool() ) { \ EnforceCompetitiveCVar( #convarName , __VA_ARGS__ ); }\ else {\ CCompetitiveCvarManager::GetConVarBackupKV()->SetFloat( #convarName, ConVarRef( #convarName ).GetFloat() ); } } \ virtual void BackupConvar() { CCompetitiveCvarManager::GetConVarBackupKV()->SetFloat( #convarName, ConVarRef( #convarName ).GetFloat() ); } \ virtual void EnforceRestrictions() { EnforceCompetitiveCVar( #convarName , __VA_ARGS__ ); } \ virtual void RestoreOriginalValue() { ConVarRef(#convarName).SetValue(CCompetitiveCvarManager::GetConVarBackupKV()->GetFloat( #convarName ) ); } \ virtual void InstallChangeCallback() { static_cast(ConVarRef( #convarName ).GetLinkedConVar())->InstallChangeCallback( CCompetitiveMinspecConvar##convarName::on_changed_##convarName); } \ }; \ static CCompetitiveMinspecConvar##convarName *s_pCompetitiveConvar##convarName = new CCompetitiveMinspecConvar##convarName(); //----------------------------------------------------------------------------- // Callback function for sv_competitive_minspec convar value change. //----------------------------------------------------------------------------- void sv_competitive_minspec_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) { ConVar *pCvar = static_cast(var); if( pCvar->GetBool() == true && (bool)flOldValue == false ) { // Backup the values of each cvar and enforce new ones CCompetitiveCvarManager::BackupAllConvars(); CCompetitiveCvarManager::EnforceRestrictionsOnAllConvars(); } else if( pCvar->GetBool() == false && (bool)flOldValue == true ) { // If sv_competitive_minspec is disabled, restore old client values CCompetitiveCvarManager::RestoreAllOriginalValues(); } } #endif static ConVar sv_competitive_minspec( "sv_competitive_minspec", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Enable to force certain client convars to minimum/maximum values to help prevent competitive advantages:\n \ r_drawdetailprops = 1\n \ r_staticprop_lod = minimum -1 maximum 3\n \ fps_max minimum 59 (0 works too)\n \ cl_detailfade minimum 400\n \ cl_detaildist minimum 1200\n \ cl_interp_ratio = minimum 1 maximum 2\n \ cl_interp = minimum 0 maximum 0.031\n \ " #ifdef CLIENT_DLL ,sv_competitive_minspec_changed_f #endif ); #ifdef CLIENT_DLL ENABLE_COMPETITIVE_CONVAR( r_drawdetailprops, true, true ); // force r_drawdetailprops on ENABLE_COMPETITIVE_CONVAR( r_staticprop_lod, -1, 3 ); // force r_staticprop_lod from -1 to 3 ENABLE_COMPETITIVE_CONVAR( fps_max, 59, FLT_MAX, 1, 0 ); // force fps_max above 59. One additional value (0) works ENABLE_COMPETITIVE_CONVAR( cl_detailfade, 400 ); // force cl_detailfade above 400. ENABLE_COMPETITIVE_CONVAR( cl_detaildist, 1200 ); // force cl_detaildist above 1200. ENABLE_COMPETITIVE_CONVAR( cl_interp_ratio, 1, 2 ); // force cl_interp_ratio from 1 to 2 ENABLE_COMPETITIVE_CONVAR( cl_interp, 0, 0.031 ); // force cl_interp from 0.0152 to 0.031 // Stubs for replay client code const char *GetMapDisplayName( const char *pMapName ) { return pMapName; } bool IsTakingAFreezecamScreenshot() { return false; } #endif