Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5859 lines
162 KiB

//========= 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<CTeam*>(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<int>(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<CPlantedC4*> (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;i<NUM_AI_CLASSES;i++)
{
for (j=0;j<NUM_AI_CLASSES;j++)
{
// By default all relationships are neutral of priority zero
CBaseCombatCharacter::SetDefaultRelationship( (Class_T)i, (Class_T)j, D_NU, 0 );
}
}
}
//------------------------------------------------------------------------------
// Purpose : Return classify text for classify type
//------------------------------------------------------------------------------
const char *CCSGameRules::AIClassText(int classType)
{
switch (classType)
{
case CLASS_NONE: return "CLASS_NONE";
case CLASS_PLAYER: return "CLASS_PLAYER";
default: return "MISSING CLASS in ClassifyText()";
}
}
//-----------------------------------------------------------------------------
// Purpose: When gaining new technologies in TF, prevent auto switching if we
// receive a weapon during the switch
// Input : *pPlayer -
// *pWeapon -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CCSGameRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon )
{
bool bIsBeingGivenItem = false;
CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
if ( pCSPlayer && pCSPlayer->IsBeingGivenItem() )
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; i<iNumHostages; i++ )
{
hostage = g_Hostages[i];
if ( hostage->m_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><TERRORIST>\" 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;i<g_Hostages.Count();i++ )
g_Hostages[i]->AcceptInput( "CTsWin", NULL, NULL, emptyVariant, 0 );
}
else if ( iWinnerTeam == WINNER_TER )
{
for( int i=0;i<g_Hostages.Count();i++ )
g_Hostages[i]->AcceptInput( "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<ICompetitiveConvar*> 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<ConVar*>(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<ConVar*>(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