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
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
|
|
|