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.
18555 lines
633 KiB
18555 lines
633 KiB
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: The TF Game rules
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#ifdef POSIX
|
|
//
|
|
// Crazy compiler problems fix -- deque must be included first before any of the Valve game headers, which introduce
|
|
// macro defines that completely break STL compile process. STL include dependencies happen as part of RSA crypto
|
|
// library dependencies included at the bottom of this file.
|
|
//
|
|
#include <deque>
|
|
#endif
|
|
|
|
#include "cbase.h"
|
|
#include "cs_gamerules.h"
|
|
#include "cs_ammodef.h"
|
|
#include "weapon_csbase.h"
|
|
#include "basecsgrenade_projectile.h"
|
|
#include "cs_shareddefs.h"
|
|
#include "keyvalues.h"
|
|
#include "cs_achievement_constants.h"
|
|
#include "iachievementmgr.h"
|
|
#include "matchmaking/imatchframework.h"
|
|
#include "inputsystem/iinputsystem.h"
|
|
#include "platforminputdevice.h"
|
|
#include "engine/inetsupport.h"
|
|
#include "usermessages.h"
|
|
#include "hegrenade_projectile.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "Effects/inferno.h"
|
|
#endif
|
|
#include "econ_item_view_helpers.h"
|
|
|
|
#ifdef CLIENT_DLL
|
|
#include "networkstringtable_clientdll.h"
|
|
#include "c_cs_player.h"
|
|
#include "fmtstr.h"
|
|
#include "vgui/ILocalize.h" // temp - needed for GetFriendlyMapName()
|
|
#include "c_team.h"
|
|
#include "weapon_selection.h"
|
|
#include "hud_macros.h"
|
|
#include "c_cs_playerresource.h"
|
|
#else
|
|
#include "baseentity.h"
|
|
#include "vote_controller.h"
|
|
#include "cs_voteissues.h"
|
|
#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 "func_bomb_target.h"
|
|
#include "func_hostage_rescue.h"
|
|
#include "dedicated_server_ugc_manager.h"
|
|
#include "vstdlib/vstrtools.h"
|
|
#include "platforminputdevice.h"
|
|
#include "cs_entity_spotting.h"
|
|
#include "props.h"
|
|
#include "hltvdirector.h"
|
|
#include "econ_gcmessages.h"
|
|
#include "env_cascade_light.h"
|
|
#include "world.h"
|
|
#include "items.h"
|
|
#include "Effects/chicken.h"
|
|
#endif
|
|
|
|
#include "gametypes.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
|
|
|
|
#define ROUND_END_WARNING_TIME 10.0f
|
|
static const float MAX_TIME_TO_WAIT_BEFORE_ENTERING = 5.0f;
|
|
|
|
// # seconds to delay before displaying autobalance text to clients
|
|
static const float AUTOBALANCE_TEXT_DELAY = 3.0f;
|
|
|
|
extern IUploadGameStats *gamestatsuploader;
|
|
extern bool Commentary_IsCommentaryEntity( CBaseEntity *pEntity );
|
|
ConVar spec_replay_round_delay( "spec_replay_round_delay", "0", FCVAR_RELEASE, "Round can be delayed by this much due to someone watching a replay; must be at least 3-4 seconds, otherwise the last replay will always be interrupted by round start, assuming normal pause between round_end and round_start events (7 seconds) and freezecam delay (2 seconds) and 7.4 second full replay (5.4 second pre-death and ~2 seconds post-death) and replay in/out switching (up to a second)" );
|
|
|
|
#endif // !CLIENT_DLL
|
|
|
|
const float g_flWarmupToFreezetimeDelay = 4.0f;
|
|
|
|
ConVar sv_server_graphic1( "sv_server_graphic1", "", FCVAR_REPLICATED | FCVAR_RELEASE, "A 360x60 (<16kb) image file in /csgo/ that will be displayed to spectators." );
|
|
ConVar sv_server_graphic2( "sv_server_graphic2", "", FCVAR_REPLICATED | FCVAR_RELEASE, "A 220x45 (<16kb) image file in /csgo/ that will be displayed to spectators." );
|
|
|
|
ConVar sv_disable_observer_interpolation( "sv_disable_observer_interpolation", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Disallow interpolating between observer targets on this server." );
|
|
|
|
ConVar sv_reward_drop_delay( "sv_reward_drop_delay", "3.0", FCVAR_REPLICATED, "Delay between the end match scoreboard being shown and the beginning of item drops." );
|
|
|
|
// to define which holiday it is
|
|
ConVar sv_holiday_mode( "sv_holiday_mode", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "0 = OFF, 1 = Halloween, 2 = Winter" );
|
|
|
|
ConVar sv_teamid_overhead_always_prohibit( "sv_teamid_overhead_always_prohibit", "0", FCVAR_RELEASE | FCVAR_REPLICATED | FCVAR_NOTIFY, "Determines whether cl_teamid_overhead_always is prohibited." );
|
|
|
|
ConVar sv_show_team_equipment_prohibit( "sv_show_team_equipment_prohibit", "0", FCVAR_RELEASE | FCVAR_REPLICATED | FCVAR_NOTIFY, "Determines whether +cl_show_team_equipment is prohibited." );
|
|
|
|
|
|
|
|
#if defined( GAME_DLL )
|
|
ConVar sv_kick_players_with_cooldown( "sv_kick_players_with_cooldown", "1", FCVAR_RELEASE | FCVAR_GAMEDLL | FCVAR_REPLICATED,
|
|
"(0: do not kick on insecure servers; 1: kick players with Untrusted status or convicted by Overwatch; 2: kick players with any cooldown)" );
|
|
|
|
ConVar sv_matchend_drops_enabled( "sv_matchend_drops_enabled", "1", FCVAR_RELEASE | FCVAR_GAMEDLL,
|
|
"Rewards gameplay time is always accumulated for players, but drops at the end of the match can be prevented" );
|
|
|
|
ConVar sv_buy_status_override( "sv_buy_status_override", "-1", FCVAR_RELEASE | FCVAR_GAMEDLL | FCVAR_REPLICATED, "Override for buy status map info. 0 = everyone can buy, 1 = ct only, 2 = t only 3 = nobody" );
|
|
ConVar sv_ct_spawn_on_bombsite( "sv_ct_spawn_on_bombsite", "-1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Force cts to spawn on a bombsite" );
|
|
|
|
ConVar sv_auto_adjust_bot_difficulty("sv_auto_adjust_bot_difficulty", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Adjust the difficulty of bots each round based on contribution score." );
|
|
ConVar sv_bots_get_easier_each_win("sv_bots_get_easier_each_win", "0", FCVAR_RELEASE | FCVAR_GAMEDLL, "If > 0, some # of bots will lower thier difficulty each time they win. The argument defines how many will lower their difficulty each time." );
|
|
ConVar sv_bots_get_harder_after_each_wave( "sv_bots_get_harder_after_each_wave", "0", FCVAR_RELEASE | FCVAR_GAMEDLL, "If > 0, some # of bots will raise thier difficulty each time CTs beat a Guardian wave. The argument defines how many will raise their difficulty each time" );
|
|
ConVar sv_bots_force_rebuy_every_round( "sv_bots_force_rebuy_every_round", "0", FCVAR_RELEASE | FCVAR_GAMEDLL, "If set, this strips the bots of their weapons every round and forces them to rebuy." );
|
|
|
|
#endif
|
|
|
|
|
|
ConVar mp_team_timeout_time( "mp_team_timeout_time", "60", FCVAR_RELEASE | FCVAR_GAMEDLL | FCVAR_REPLICATED, "Duration of each timeout." );
|
|
ConVar mp_team_timeout_max( "mp_team_timeout_max", "1", FCVAR_RELEASE | FCVAR_GAMEDLL | FCVAR_REPLICATED, "Number of timeouts each team gets per match." );
|
|
|
|
|
|
void MaxAllowedNetGraphCallback( IConVar *var, const char *pOldValue, float flOldValue );
|
|
ConVar sv_max_allowed_net_graph( "sv_max_allowed_net_graph", "1", FCVAR_NOTIFY | FCVAR_RELEASE | FCVAR_REPLICATED, "Determines max allowed net_graph value for clients.", MaxAllowedNetGraphCallback );
|
|
|
|
void MaxAllowedNetGraphCallback( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
extern ConVar net_graph;
|
|
|
|
if ( net_graph.GetInt() > sv_max_allowed_net_graph.GetInt() )
|
|
{
|
|
net_graph.SetValue( sv_max_allowed_net_graph.GetInt() );
|
|
Msg( "Server does not allow net_graph values above %d\n", sv_max_allowed_net_graph.GetInt() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#define SV_QMM_MIN_PLAYERS_FOR_CANCEL_MATCH 0
|
|
|
|
extern ConVar mp_maxrounds;
|
|
extern ConVar mp_match_restart_delay;
|
|
|
|
ConVar sv_disable_show_team_select_menu( "sv_disable_show_team_select_menu", "0", FCVAR_REPLICATED|FCVAR_RELEASE, "Prevent the team select menu from showing." );
|
|
ConVar sv_disable_motd( "sv_disable_motd", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Prevent the motd from showing." );
|
|
|
|
#ifdef CLIENT_DLL
|
|
CON_COMMAND_F( print_mapgroup, "Prints the current mapgroup and the contained maps", FCVAR_RELEASE )
|
|
#else
|
|
CON_COMMAND_F( print_mapgroup_sv, "Prints the current mapgroup and the contained maps", FCVAR_RELEASE )
|
|
#endif
|
|
{
|
|
#if defined ( CLIENT_DLL )
|
|
const char* szMapGroup = engine->GetMapGroupName();
|
|
#else
|
|
const char* szMapGroup = STRING( gpGlobals->mapGroupName );
|
|
#endif
|
|
const CUtlStringList *pMaps = g_pGameTypes->GetMapGroupMapList( szMapGroup );
|
|
Msg( "Map group: %s\n", szMapGroup );
|
|
if ( pMaps )
|
|
{
|
|
FOR_EACH_VEC( *pMaps, i )
|
|
{
|
|
const char* szMap = (*pMaps)[i];
|
|
Msg( " %s\n", szMap );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Msg( "No maps in mapgroup map list!\n");
|
|
}
|
|
}
|
|
|
|
const float cInitialRestartRoundTime = 0.0f;
|
|
|
|
|
|
/**
|
|
* 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, 72 ), // hull max
|
|
|
|
Vector(-16, -16, 0 ), // duck hull min
|
|
Vector( 16, 16, 54 ), // duck hull max
|
|
Vector( 0, 0, 46 ), // 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
|
|
|
|
extern ConVar spec_replay_bot;
|
|
|
|
BEGIN_DATADESC( SpawnPoint )
|
|
// Keyfields
|
|
DEFINE_KEYFIELD( m_iPriority, FIELD_INTEGER, "priority" ),
|
|
DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "SetEnabled", InputSetEnabled ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "SetDisabled", InputSetDisabled ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "ToggleEnabled", InputToggleEnabled ),
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( info_player_terrorist, SpawnPoint );
|
|
LINK_ENTITY_TO_CLASS( info_player_counterterrorist, SpawnPoint );
|
|
LINK_ENTITY_TO_CLASS( info_player_logo, CPointEntity );
|
|
LINK_ENTITY_TO_CLASS( info_deathmatch_spawn, SpawnPoint );
|
|
LINK_ENTITY_TO_CLASS( info_armsrace_counterterrorist, SpawnPoint );
|
|
LINK_ENTITY_TO_CLASS( info_armsrace_terrorist, SpawnPoint );
|
|
|
|
template< typename TIssue >
|
|
void NewTeamIssue()
|
|
{
|
|
new TIssue( g_voteControllerT );
|
|
new TIssue( g_voteControllerCT );
|
|
}
|
|
|
|
template< typename TIssue >
|
|
void NewGlobalIssue()
|
|
{
|
|
new TIssue( g_voteControllerGlobal );
|
|
}
|
|
|
|
|
|
SpawnPoint::SpawnPoint() : m_bEnabled( true ), m_nType( 0 )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void SpawnPoint::Spawn( void )
|
|
{
|
|
BaseClass::Spawn();
|
|
|
|
if ( CSGameRules() )
|
|
CSGameRules()->AddSpawnPointToMasterList( this );
|
|
}
|
|
|
|
void SpawnPoint::InputSetEnabled( inputdata_t &inputdata )
|
|
{
|
|
SetSpawnEnabled( true );
|
|
}
|
|
|
|
void SpawnPoint::InputSetDisabled( inputdata_t &inputdata )
|
|
{
|
|
SetSpawnEnabled( false );
|
|
}
|
|
|
|
void SpawnPoint::InputToggleEnabled( inputdata_t &inputdata )
|
|
{
|
|
m_bEnabled = !m_bEnabled;
|
|
|
|
if ( CSGameRules() &&
|
|
!CSGameRules()->IsPlayingCoopMission() )
|
|
{
|
|
CSGameRules()->RefreshCurrentSpawnPointLists();
|
|
}
|
|
}
|
|
|
|
void SpawnPoint::SetSpawnEnabled( bool bEnabled )
|
|
{
|
|
bool bChanged = (m_bEnabled != bEnabled);
|
|
|
|
m_bEnabled = bEnabled;
|
|
|
|
if ( CSGameRules() && bChanged &&
|
|
!CSGameRules()->IsPlayingCoopMission() )
|
|
{
|
|
CSGameRules()->RefreshCurrentSpawnPointLists();
|
|
}
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS( info_enemy_terrorist_spawn, SpawnPointCoopEnemy );
|
|
|
|
BEGIN_DATADESC( SpawnPointCoopEnemy )
|
|
DEFINE_KEYFIELD( m_szWeaponsToGive, FIELD_STRING, "weapons_to_give" ),
|
|
DEFINE_KEYFIELD( m_szPlayerModelToUse, FIELD_STRING, "model_to_use" ),
|
|
DEFINE_KEYFIELD( m_nArmorToSpawnWith, FIELD_INTEGER, "armor_to_give" ),
|
|
DEFINE_KEYFIELD( m_nDefaultBehavior, FIELD_INTEGER, "default_behavior" ),
|
|
DEFINE_KEYFIELD( m_nBotDifficulty, FIELD_INTEGER, "bot_difficulty" ),
|
|
DEFINE_KEYFIELD( m_bIsAgressive, FIELD_BOOLEAN, "is_agressive" ),
|
|
DEFINE_KEYFIELD( m_bStartAsleep, FIELD_BOOLEAN, "start_asleep" ),
|
|
DEFINE_KEYFIELD( m_flHideRadius, FIELD_FLOAT, "hide_radius" ),
|
|
END_DATADESC()
|
|
|
|
SpawnPointCoopEnemy::SpawnPointCoopEnemy( void )
|
|
{
|
|
//m_szWeaponsToGive = NULL_STRING;
|
|
Assert( CSGameRules()->IsPlayingCoopMission() );
|
|
m_pMyArea = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void SpawnPointCoopEnemy::Spawn( void )
|
|
{
|
|
BaseClass::Spawn();
|
|
Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void SpawnPointCoopEnemy::Precache( void )
|
|
{
|
|
if ( V_stricmp( GetPlayerModelToUse(), "" ) != 0 )
|
|
{
|
|
PrecacheModel( GetPlayerModelToUse() );
|
|
}
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
CNavArea * SpawnPointCoopEnemy::FindNearestArea( void )
|
|
{
|
|
// These don't move so only do the expensive find the first time
|
|
if ( !m_pMyArea )
|
|
m_pMyArea = TheNavMesh->GetNearestNavArea( this );
|
|
|
|
return m_pMyArea;
|
|
}
|
|
|
|
|
|
class CPointGiveAmmo : public CPointEntity
|
|
{
|
|
DECLARE_CLASS( CPointGiveAmmo, CPointEntity );
|
|
|
|
public:
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
|
|
// Input handlers
|
|
void InputGiveAmmo( inputdata_t &inputdata );
|
|
|
|
DECLARE_DATADESC();
|
|
|
|
// int m_nDamage;
|
|
// int m_bitsDamageType;
|
|
// float m_flRadius;
|
|
// float m_flDelay;
|
|
// string_t m_strTarget;
|
|
EHANDLE m_pActivator;
|
|
};
|
|
|
|
BEGIN_DATADESC( CPointGiveAmmo )
|
|
|
|
// DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "DamageRadius" ),
|
|
// DEFINE_KEYFIELD( m_nDamage, FIELD_INTEGER, "Damage" ),
|
|
// DEFINE_KEYFIELD( m_flDelay, FIELD_FLOAT, "DamageDelay" ),
|
|
// DEFINE_KEYFIELD( m_bitsDamageType, FIELD_INTEGER, "DamageType" ),
|
|
// DEFINE_KEYFIELD( m_strTarget, FIELD_STRING, "DamageTarget" ),
|
|
|
|
// Function Pointers
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "GiveAmmo", InputGiveAmmo ),
|
|
|
|
DEFINE_FIELD( m_pActivator, FIELD_EHANDLE ),
|
|
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( point_give_ammo, CPointGiveAmmo );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CPointGiveAmmo::Spawn( void )
|
|
{
|
|
SetThink( NULL );
|
|
SetUse( NULL );
|
|
|
|
m_pActivator = NULL;
|
|
|
|
Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CPointGiveAmmo::Precache( void )
|
|
{
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for instantaneously hurting whatever is near us.
|
|
//-----------------------------------------------------------------------------
|
|
void CPointGiveAmmo::InputGiveAmmo( inputdata_t &data )
|
|
{
|
|
m_pActivator = data.pActivator;
|
|
|
|
CBasePlayer *pPlayer = dynamic_cast< CBasePlayer* >( m_pActivator.Get() );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
for ( int i = 0; i < 2; ++i )
|
|
{
|
|
CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pPlayer->Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
|
|
if ( i == 1 )
|
|
pWeapon = dynamic_cast< CWeaponCSBase* >( pPlayer->Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
|
|
|
|
if ( pWeapon )
|
|
{
|
|
if ( pWeapon->UsesPrimaryAmmo() )
|
|
{
|
|
int ammoIndex = pWeapon->GetPrimaryAmmoType();
|
|
|
|
if ( ammoIndex != -1 )
|
|
{
|
|
int giveAmount;
|
|
giveAmount = GetAmmoDef()->MaxCarry( ammoIndex, pPlayer );
|
|
pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex( ammoIndex )->pName );
|
|
}
|
|
}
|
|
if ( pWeapon->UsesSecondaryAmmo() && pWeapon->HasSecondaryAmmo() )
|
|
{
|
|
// Give secondary ammo out, as long as the player already has some
|
|
// from a presumeably natural source. This prevents players on XBox
|
|
// having Combine Balls and so forth in areas of the game that
|
|
// were not tested with these items.
|
|
int ammoIndex = pWeapon->GetSecondaryAmmoType();
|
|
|
|
if ( ammoIndex != -1 )
|
|
{
|
|
int giveAmount;
|
|
giveAmount = GetAmmoDef()->MaxCarry( ammoIndex, pPlayer );
|
|
pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex( ammoIndex )->pName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class CCoopBonusCoin : public CDynamicProp
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CCoopBonusCoin, CDynamicProp );
|
|
DECLARE_DATADESC();
|
|
|
|
void CoinTouch( CBaseEntity *pOther );
|
|
void CoinFadeOut( void );
|
|
//void CoinThink( void );
|
|
void Precache( void );
|
|
void Spawn( void ) OVERRIDE;
|
|
void StartFadeOut( float delay );
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( item_coop_coin, CCoopBonusCoin );
|
|
PRECACHE_REGISTER( item_coop_coin );
|
|
|
|
BEGIN_DATADESC( CCoopBonusCoin )
|
|
// Function Pointers
|
|
DEFINE_ENTITYFUNC( CoinTouch ),
|
|
DEFINE_THINKFUNC( CoinFadeOut ),
|
|
END_DATADESC()
|
|
|
|
void CCoopBonusCoin::CoinTouch( CBaseEntity *pOther )
|
|
{
|
|
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pOther );
|
|
if ( !pPlayer || !CSGameRules() )
|
|
return;
|
|
|
|
CSGameRules()->CoopCollectBonusCoin();
|
|
EmitSound( "CollectableCoin.Collect" );
|
|
|
|
ResetSequence( LookupSequence( "challenge_coin_collect" ) );
|
|
|
|
SetTouch( NULL );
|
|
|
|
StartFadeOut( 0.125 );
|
|
}
|
|
|
|
// void CCoopBonusCoin::CoinThink( void )
|
|
// {
|
|
// StudioFrameAdvance();
|
|
// }
|
|
|
|
void CCoopBonusCoin::Precache( void )
|
|
{
|
|
PrecacheModel( "models/coop/challenge_coin.mdl" );
|
|
PrecacheScriptSound( "CollectableCoin.Collect" );
|
|
}
|
|
|
|
void CCoopBonusCoin::Spawn( void )
|
|
{
|
|
Precache();
|
|
SetModel( "models/coop/challenge_coin.mdl" );
|
|
SetModelName( MAKE_STRING( "models/coop/challenge_coin.mdl" ) );
|
|
|
|
BaseClass::Spawn();
|
|
|
|
//m_bShouldGlow = true;
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetSolid( SOLID_BBOX );
|
|
SetBlocksLOS( false );
|
|
AddSolidFlags( FSOLID_TRIGGER );
|
|
AddEFlags( EFL_NO_ROTORWASH_PUSH );
|
|
|
|
SetCollisionGroup( COLLISION_GROUP_WEAPON );
|
|
//if ( HasBloatedCollision() )
|
|
//{
|
|
//CollisionProp()->UseTriggerBounds( true, ITEM_PICKUP_BOX_BLOAT );
|
|
//}
|
|
SetTouch( &CCoopBonusCoin::CoinTouch );
|
|
|
|
ResetSequence( LookupSequence( "idle" ) );
|
|
|
|
//SetThink( &CCoopBonusCoin::CoinThink );
|
|
}
|
|
|
|
void CCoopBonusCoin::StartFadeOut( float delay )
|
|
{
|
|
SetThink( &CCoopBonusCoin::CoinFadeOut );
|
|
SetNextThink( gpGlobals->curtime + delay );
|
|
SetRenderAlpha( 255 );
|
|
m_nRenderMode = kRenderNormal;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fade out slowly
|
|
//-----------------------------------------------------------------------------
|
|
void CCoopBonusCoin::CoinFadeOut( void )
|
|
{
|
|
float dt = gpGlobals->frametime;
|
|
if ( dt > 0.1f )
|
|
dt = 0.1f;
|
|
|
|
m_nRenderMode = kRenderTransTexture;
|
|
int speed = MAX( 0.35, 256 * dt ); // fade out
|
|
SetRenderAlpha( UTIL_Approach( 0, m_clrRender->a, speed ) );
|
|
|
|
if ( m_clrRender->a == 0 )
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
else
|
|
{
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined ( CLIENT_DLL )
|
|
bool __MsgFunc_SendPlayerItemDrops( const CCSUsrMsg_SendPlayerItemDrops &msg );
|
|
bool __MsgFunc_SendPlayerItemFound( const CCSUsrMsg_SendPlayerItemFound &msg );
|
|
#endif
|
|
|
|
|
|
REGISTER_GAMERULES_CLASS( CCSGameRules );
|
|
|
|
BEGIN_NETWORK_TABLE_NOBASE( CCSGameRules, DT_CSGameRules )
|
|
#ifdef CLIENT_DLL
|
|
RecvPropBool( RECVINFO( m_bFreezePeriod ) ),
|
|
RecvPropBool( RECVINFO( m_bMatchWaitingForResume ) ),
|
|
RecvPropBool( RECVINFO( m_bWarmupPeriod ) ),
|
|
RecvPropFloat( RECVINFO( m_fWarmupPeriodEnd ) ), // DUMMY VAR FOR DEMOS
|
|
RecvPropFloat( RECVINFO( m_fWarmupPeriodStart ) ),
|
|
|
|
RecvPropBool( RECVINFO( m_bTerroristTimeOutActive ) ),
|
|
RecvPropBool( RECVINFO( m_bCTTimeOutActive ) ),
|
|
RecvPropFloat( RECVINFO( m_flTerroristTimeOutRemaining ) ),
|
|
RecvPropFloat( RECVINFO( m_flCTTimeOutRemaining ) ),
|
|
RecvPropInt( RECVINFO( m_nTerroristTimeOuts ) ),
|
|
RecvPropInt( RECVINFO( m_nCTTimeOuts ) ),
|
|
|
|
RecvPropInt( RECVINFO( m_iRoundTime ) ),
|
|
RecvPropInt( RECVINFO( m_gamePhase ) ),
|
|
RecvPropInt( RECVINFO( m_totalRoundsPlayed ) ),
|
|
RecvPropInt( RECVINFO( m_nOvertimePlaying ) ),
|
|
RecvPropFloat( RECVINFO( m_timeUntilNextPhaseStarts ) ),
|
|
RecvPropFloat( RECVINFO( m_flCMMItemDropRevealStartTime ) ),
|
|
RecvPropFloat( RECVINFO( m_flCMMItemDropRevealEndTime ) ),
|
|
RecvPropFloat( RECVINFO( m_fRoundStartTime ) ),
|
|
RecvPropBool( RECVINFO( m_bGameRestart ) ),
|
|
RecvPropFloat( RECVINFO( m_flRestartRoundTime ) ),
|
|
RecvPropFloat( RECVINFO( m_flGameStartTime ) ),
|
|
RecvPropInt( RECVINFO( m_iHostagesRemaining ) ),
|
|
RecvPropBool( RECVINFO( m_bAnyHostageReached ) ),
|
|
RecvPropBool( RECVINFO( m_bMapHasBombTarget ) ),
|
|
RecvPropBool( RECVINFO( m_bMapHasRescueZone ) ),
|
|
RecvPropBool( RECVINFO( m_bMapHasBuyZone ) ),
|
|
RecvPropBool( RECVINFO( m_bIsQueuedMatchmaking ) ),
|
|
RecvPropBool( RECVINFO( m_bIsValveDS ) ),
|
|
RecvPropBool( RECVINFO( m_bIsQuestEligible ) ),
|
|
RecvPropBool( RECVINFO( m_bLogoMap ) ),
|
|
RecvPropInt( RECVINFO( m_iNumGunGameProgressiveWeaponsCT ) ),
|
|
RecvPropInt( RECVINFO( m_iNumGunGameProgressiveWeaponsT ) ),
|
|
RecvPropInt( RECVINFO( m_iSpectatorSlotCount ) ),
|
|
RecvPropBool( RECVINFO( m_bBombDropped ) ),
|
|
RecvPropBool( RECVINFO( m_bBombPlanted ) ),
|
|
RecvPropInt( RECVINFO( m_iRoundWinStatus ) ),
|
|
RecvPropInt( RECVINFO( m_eRoundWinReason ) ),
|
|
RecvPropFloat( RECVINFO( m_flDMBonusStartTime ) ),
|
|
RecvPropFloat( RECVINFO( m_flDMBonusTimeLength ) ),
|
|
RecvPropInt( RECVINFO( m_unDMBonusWeaponLoadoutSlot ) ),
|
|
RecvPropBool( RECVINFO( m_bDMBonusActive ) ),
|
|
RecvPropBool( RECVINFO( m_bTCantBuy ) ),
|
|
RecvPropBool( RECVINFO( m_bCTCantBuy ) ),
|
|
RecvPropFloat( RECVINFO( m_flGuardianBuyUntilTime ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_iMatchStats_RoundResults ), RecvPropInt( RECVINFO( m_iMatchStats_RoundResults[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_iMatchStats_PlayersAlive_T ), RecvPropInt( RECVINFO( m_iMatchStats_PlayersAlive_T[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_iMatchStats_PlayersAlive_CT ), RecvPropInt( RECVINFO( m_iMatchStats_PlayersAlive_CT[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_GGProgressiveWeaponOrderCT ), RecvPropInt( RECVINFO( m_GGProgressiveWeaponOrderCT[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_GGProgressiveWeaponOrderT ), RecvPropInt( RECVINFO( m_GGProgressiveWeaponOrderT[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_GGProgressiveWeaponKillUpgradeOrderCT ), RecvPropInt( RECVINFO( m_GGProgressiveWeaponKillUpgradeOrderCT[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_GGProgressiveWeaponKillUpgradeOrderT ), RecvPropInt( RECVINFO( m_GGProgressiveWeaponKillUpgradeOrderT[0] ) ) ),
|
|
RecvPropInt( RECVINFO( m_MatchDevice ) ),
|
|
RecvPropBool( RECVINFO( m_bHasMatchStarted ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY(m_TeamRespawnWaveTimes), RecvPropFloat( RECVINFO(m_TeamRespawnWaveTimes[0]) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY(m_flNextRespawnWave), RecvPropTime( RECVINFO(m_flNextRespawnWave[0]) ) ),
|
|
RecvPropInt( RECVINFO( m_nNextMapInMapgroup ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_nEndMatchMapGroupVoteOptions ), RecvPropInt( RECVINFO( m_nEndMatchMapGroupVoteOptions[0] ) ) ),
|
|
RecvPropBool( RECVINFO( m_bIsDroppingItems ) ),
|
|
RecvPropInt( RECVINFO( m_iActiveAssassinationTargetMissionID ) ),
|
|
RecvPropFloat( RECVINFO( m_fMatchStartTime ) ),
|
|
RecvPropString( RECVINFO( m_szTournamentEventName ) ),
|
|
RecvPropString( RECVINFO( m_szTournamentEventStage ) ),
|
|
RecvPropString( RECVINFO( m_szTournamentPredictionsTxt ) ),
|
|
RecvPropInt( RECVINFO( m_nTournamentPredictionsPct ) ),
|
|
RecvPropString( RECVINFO( m_szMatchStatTxt ) ),
|
|
|
|
// guardian mode
|
|
RecvPropInt( RECVINFO( m_nGuardianModeWaveNumber ) ),
|
|
RecvPropInt( RECVINFO( m_nGuardianModeSpecialKillsRemaining ) ),
|
|
RecvPropInt( RECVINFO( m_nGuardianModeSpecialWeaponNeeded ) ),
|
|
|
|
// halloween
|
|
RecvPropInt( RECVINFO( m_nHalloweenMaskListSeed ) ),
|
|
|
|
// Gifts global info
|
|
RecvPropInt( RECVINFO( m_numGlobalGiftsGiven ) ),
|
|
RecvPropInt( RECVINFO( m_numGlobalGifters ) ),
|
|
RecvPropInt( RECVINFO( m_numGlobalGiftsPeriodSeconds ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_arrFeaturedGiftersAccounts ), RecvPropInt (RECVINFO( m_arrFeaturedGiftersAccounts[0] ) ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_arrFeaturedGiftersGifts ), RecvPropInt (RECVINFO( m_arrFeaturedGiftersGifts[0] ) ) ),
|
|
|
|
RecvPropArray3( RECVINFO_ARRAY( m_arrProhibitedItemIndices ), RecvPropInt( RECVINFO( m_arrProhibitedItemIndices[ 0 ] ) ) ),
|
|
|
|
// Tournament Casters
|
|
RecvPropInt( RECVINFO( m_numBestOfMaps ) ),
|
|
RecvPropArray3( RECVINFO_ARRAY( m_arrTournamentActiveCasterAccounts ), RecvPropInt ( RECVINFO( m_arrTournamentActiveCasterAccounts[0] ), 0, CCSGameRules::RecvProxy_TournamentActiveCasterAccounts ) )
|
|
#else
|
|
SendPropBool( SENDINFO( m_bFreezePeriod ) ),
|
|
SendPropBool( SENDINFO( m_bMatchWaitingForResume ) ),
|
|
SendPropBool( SENDINFO( m_bWarmupPeriod ) ),
|
|
SendPropFloat( SENDINFO( m_fWarmupPeriodEnd ) ), // DUMMY VAR FOR DEMOS
|
|
SendPropFloat( SENDINFO( m_fWarmupPeriodStart ) ),
|
|
|
|
SendPropBool( SENDINFO( m_bTerroristTimeOutActive ) ),
|
|
SendPropBool( SENDINFO( m_bCTTimeOutActive ) ),
|
|
SendPropFloat( SENDINFO( m_flTerroristTimeOutRemaining ) ),
|
|
SendPropFloat( SENDINFO( m_flCTTimeOutRemaining ) ),
|
|
SendPropInt( SENDINFO( m_nTerroristTimeOuts ) ),
|
|
SendPropInt( SENDINFO( m_nCTTimeOuts ) ),
|
|
|
|
SendPropInt( SENDINFO( m_iRoundTime ), 16 ),
|
|
SendPropInt( SENDINFO( m_gamePhase ), 4, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO( m_totalRoundsPlayed ), 16 ),
|
|
SendPropInt( SENDINFO( m_nOvertimePlaying ), 16 ),
|
|
SendPropFloat( SENDINFO( m_timeUntilNextPhaseStarts ), 32, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_flCMMItemDropRevealStartTime ), 32, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_flCMMItemDropRevealEndTime ), 32, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_fRoundStartTime ), 32, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_flRestartRoundTime ) ),
|
|
SendPropBool( SENDINFO( m_bGameRestart ) ),
|
|
SendPropFloat( SENDINFO( m_flGameStartTime ), 32, SPROP_NOSCALE ),
|
|
SendPropInt( SENDINFO( m_iHostagesRemaining ), 4 ),
|
|
SendPropBool( SENDINFO( m_bAnyHostageReached ) ),
|
|
SendPropBool( SENDINFO( m_bMapHasBombTarget ) ),
|
|
SendPropBool( SENDINFO( m_bMapHasRescueZone ) ),
|
|
SendPropBool( SENDINFO( m_bMapHasBuyZone ) ),
|
|
SendPropBool( SENDINFO( m_bIsQueuedMatchmaking ) ),
|
|
SendPropBool( SENDINFO( m_bIsValveDS ) ),
|
|
SendPropBool( SENDINFO( m_bIsQuestEligible ) ),
|
|
SendPropBool( SENDINFO( m_bLogoMap ) ),
|
|
SendPropInt( SENDINFO( m_iNumGunGameProgressiveWeaponsCT ) ),
|
|
SendPropInt( SENDINFO( m_iNumGunGameProgressiveWeaponsT ) ),
|
|
SendPropInt( SENDINFO( m_iSpectatorSlotCount ) ),
|
|
SendPropBool( SENDINFO( m_bBombDropped ) ),
|
|
SendPropBool( SENDINFO( m_bBombPlanted ) ),
|
|
SendPropInt( SENDINFO( m_iRoundWinStatus ) ),
|
|
SendPropInt( SENDINFO( m_eRoundWinReason ) ),
|
|
SendPropFloat( SENDINFO( m_flDMBonusStartTime ) ),
|
|
SendPropFloat( SENDINFO( m_flDMBonusTimeLength ) ),
|
|
SendPropInt( SENDINFO( m_unDMBonusWeaponLoadoutSlot ) ),
|
|
SendPropBool( SENDINFO( m_bDMBonusActive ) ),
|
|
SendPropBool( SENDINFO( m_bTCantBuy ) ),
|
|
SendPropBool( SENDINFO( m_bCTCantBuy ) ),
|
|
SendPropFloat( SENDINFO( m_flGuardianBuyUntilTime ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_iMatchStats_RoundResults ), SendPropInt (SENDINFO_ARRAY( m_iMatchStats_RoundResults ), 8, SPROP_UNSIGNED ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_iMatchStats_PlayersAlive_T ), SendPropInt (SENDINFO_ARRAY( m_iMatchStats_PlayersAlive_T ), 6, SPROP_UNSIGNED ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_iMatchStats_PlayersAlive_CT ), SendPropInt (SENDINFO_ARRAY( m_iMatchStats_PlayersAlive_CT ), 6, SPROP_UNSIGNED ) ),
|
|
|
|
SendPropArray3( SENDINFO_ARRAY3( m_GGProgressiveWeaponOrderCT ), SendPropInt (SENDINFO_ARRAY( m_GGProgressiveWeaponOrderCT ), 0, SPROP_UNSIGNED ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_GGProgressiveWeaponOrderT ), SendPropInt( SENDINFO_ARRAY( m_GGProgressiveWeaponOrderT ), 0, SPROP_UNSIGNED ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_GGProgressiveWeaponKillUpgradeOrderCT ), SendPropInt( SENDINFO_ARRAY( m_GGProgressiveWeaponKillUpgradeOrderCT ), 0, SPROP_UNSIGNED ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_GGProgressiveWeaponKillUpgradeOrderT ), SendPropInt( SENDINFO_ARRAY( m_GGProgressiveWeaponKillUpgradeOrderT ), 0, SPROP_UNSIGNED ) ),
|
|
SendPropInt( SENDINFO( m_MatchDevice ) ),
|
|
SendPropBool( SENDINFO( m_bHasMatchStarted ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3(m_TeamRespawnWaveTimes), SendPropFloat( SENDINFO_ARRAY(m_TeamRespawnWaveTimes) ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3(m_flNextRespawnWave), SendPropTime( SENDINFO_ARRAY(m_flNextRespawnWave) ) ),
|
|
SendPropInt( SENDINFO( m_nNextMapInMapgroup ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_nEndMatchMapGroupVoteOptions ), SendPropInt (SENDINFO_ARRAY( m_nEndMatchMapGroupVoteOptions ) ) ),
|
|
SendPropBool( SENDINFO( m_bIsDroppingItems ) ),
|
|
SendPropInt( SENDINFO( m_iActiveAssassinationTargetMissionID ) ),
|
|
SendPropFloat( SENDINFO( m_fMatchStartTime ), 32, SPROP_NOSCALE ),
|
|
SendPropString( SENDINFO( m_szTournamentEventName ) ),
|
|
SendPropString( SENDINFO( m_szTournamentEventStage ) ),
|
|
SendPropString( SENDINFO( m_szTournamentPredictionsTxt ) ),
|
|
SendPropInt( SENDINFO( m_nTournamentPredictionsPct ) ),
|
|
SendPropString( SENDINFO( m_szMatchStatTxt ) ),
|
|
|
|
// guardian mode
|
|
SendPropInt( SENDINFO( m_nGuardianModeWaveNumber ) ),
|
|
SendPropInt( SENDINFO( m_nGuardianModeSpecialKillsRemaining ) ),
|
|
SendPropInt( SENDINFO( m_nGuardianModeSpecialWeaponNeeded ) ),
|
|
|
|
// Halloween
|
|
SendPropInt( SENDINFO( m_nHalloweenMaskListSeed ) ),
|
|
|
|
// Gifts global info
|
|
SendPropInt( SENDINFO( m_numGlobalGiftsGiven ), 0, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO( m_numGlobalGifters ), 0, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO( m_numGlobalGiftsPeriodSeconds ), 0, SPROP_UNSIGNED ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_arrFeaturedGiftersAccounts ), SendPropInt (SENDINFO_ARRAY( m_arrFeaturedGiftersAccounts ), 0, SPROP_UNSIGNED ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_arrFeaturedGiftersGifts ), SendPropInt (SENDINFO_ARRAY( m_arrFeaturedGiftersGifts ), 0, SPROP_UNSIGNED ) ),
|
|
|
|
SendPropArray3( SENDINFO_ARRAY3( m_arrProhibitedItemIndices ), SendPropInt( SENDINFO_ARRAY( m_arrProhibitedItemIndices ), 0, SPROP_UNSIGNED ) ),
|
|
|
|
// Tournament Casters
|
|
SendPropInt( SENDINFO( m_numBestOfMaps ), 4, SPROP_UNSIGNED ), // supporting no more than best-of-7 (1+2+4)
|
|
SendPropArray3( SENDINFO_ARRAY3( m_arrTournamentActiveCasterAccounts ), SendPropInt (SENDINFO_ARRAY( m_arrTournamentActiveCasterAccounts ), 0, SPROP_UNSIGNED ) )
|
|
#endif
|
|
END_NETWORK_TABLE()
|
|
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( CSGameRulesProxy, DT_CSGameRulesProxy )
|
|
LINK_ENTITY_TO_CLASS_ALIASED( cs_gamerules, CSGameRulesProxy );
|
|
|
|
#ifdef GAME_DLL
|
|
ConVar mp_teamname_1( "mp_teamname_1", "", FCVAR_RELEASE, "A non-empty string overrides the first team's name." );
|
|
ConVar mp_teamname_2( "mp_teamname_2", "", FCVAR_RELEASE, "A non-empty string overrides the second team's name." );
|
|
|
|
ConVar mp_teamflag_1( "mp_teamflag_1", "", FCVAR_RELEASE, "Enter a country's alpha 2 code to show that flag next to team 1's name in the spectator scoreboard." );
|
|
ConVar mp_teamflag_2( "mp_teamflag_2", "", FCVAR_RELEASE, "Enter a country's alpha 2 code to show that flag next to team 2's name in the spectator scoreboard." );
|
|
|
|
ConVar mp_teamlogo_1( "mp_teamlogo_1", "", FCVAR_RELEASE, "Enter a team's shorthand image name to display their logo. Images can be found here: 'resource/flash/econ/tournaments/teams'" );
|
|
ConVar mp_teamlogo_2( "mp_teamlogo_2", "", FCVAR_RELEASE, "Enter a team's shorthand image name to display their logo. Images can be found here: 'resource/flash/econ/tournaments/teams'" );
|
|
|
|
ConVar mp_teamprediction_txt( "mp_teamprediction_txt", "#SFUIHUD_Spectate_Predictions", FCVAR_RELEASE, "A value between 1 and 99 will set predictions in favor of first team." );
|
|
ConVar mp_teamprediction_pct( "mp_teamprediction_pct", "0", FCVAR_RELEASE, "A value between 1 and 99 will show predictions in favor of CT team." );
|
|
|
|
ConVar mp_teammatchstat_txt( "mp_teammatchstat_txt", "", FCVAR_RELEASE, "A non-empty string sets the match stat description, e.g. 'Match 2 of 3'." );
|
|
ConVar mp_teammatchstat_1( "mp_teammatchstat_1", "", FCVAR_RELEASE, "A non-empty string sets first team's match stat." );
|
|
ConVar mp_teammatchstat_2( "mp_teammatchstat_2", "", FCVAR_RELEASE, "A non-empty string sets second team's match stat." );
|
|
|
|
ConVar mp_teamscore_1( "mp_teamscore_1", "", FCVAR_RELEASE, "A non-empty string for best-of-N maps won by the first team." );
|
|
ConVar mp_teamscore_2( "mp_teamscore_2", "", FCVAR_RELEASE, "A non-empty string for best-of-N maps won by the second team." );
|
|
void FnChangeCallback_mp_teamscore_max( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
ConVarRef ref( var );
|
|
int nVal = ref.GetInt();
|
|
if ( CCSGameRules *pCSR = CSGameRules() )
|
|
{
|
|
if ( nVal != pCSR->m_numBestOfMaps )
|
|
pCSR->m_numBestOfMaps = nVal;
|
|
}
|
|
}
|
|
ConVar mp_teamscore_max( "mp_teamscore_max", "0", FCVAR_RELEASE, "How many maps to win the series (bo3 max=2; bo5 max=3; bo7 max=4)", true, 0, true, 7, FnChangeCallback_mp_teamscore_max );
|
|
|
|
ConVar mp_teammatchstat_holdtime( "mp_teammatchstat_holdtime", "5", FCVAR_RELEASE, "Decide on a match stat and hold it additionally for at least so many seconds" );
|
|
ConVar mp_teammatchstat_cycletime( "mp_teammatchstat_cycletime", "45", FCVAR_RELEASE, "Cycle match stats after so many seconds" );
|
|
#endif
|
|
|
|
// COOP
|
|
#define COOPMISSION_SCORE_MULTIPLIER_TIMELEFT 40
|
|
#define COOPMISSION_SCORE_MULTIPLIER_DAMTAKEN -10
|
|
#define COOPMISSION_SCORE_MULTIPLIER_ROUNDSFAILED -1000
|
|
|
|
static uint32 Helper_ScoreLeaderboardData_FindEntryValue( uint32 nTag, const ::google::protobuf::RepeatedPtrField< ::ScoreLeaderboardData_Entry >&arr )
|
|
{
|
|
for ( int i = 0; i < arr.size(); ++ i )
|
|
{
|
|
if ( arr.Get( i ).tag() == nTag )
|
|
return arr.Get( i ).val();
|
|
}
|
|
return 0;
|
|
}
|
|
static uint32 Helper_ScoreLeaderboardData_FindEntryValueSum( uint32 nTag, const ::google::protobuf::RepeatedPtrField< ::ScoreLeaderboardData_AccountEntries >&arr )
|
|
{
|
|
uint32 val = 0;
|
|
for ( int i = 0; i < arr.size(); ++i )
|
|
{
|
|
val += Helper_ScoreLeaderboardData_FindEntryValue( nTag, arr.Get( i ).entries() );
|
|
}
|
|
return val;
|
|
}
|
|
|
|
int32 CoopScoreGetRatingEntryFromLeaderboardData( ScoreLeaderboardData &sld, bool bBonus, int nIndex, bool bAsScore )
|
|
{ // Note: this function should not be referencing gamerules because we can be looking at scores from Steam friends leaderboards
|
|
bool bGuardian = false;
|
|
bool bCoopMission = true;
|
|
if ( sld.quest_id() )
|
|
{
|
|
const CEconQuestDefinition *pQuest = GetItemSchema()->GetQuestDefinition( sld.quest_id() );
|
|
if ( !pQuest )
|
|
return 0.0f;
|
|
if ( !V_stricmp( pQuest->GetGameMode(), "cooperative" ) )
|
|
{
|
|
bGuardian = true;
|
|
bCoopMission = false;
|
|
}
|
|
// otherwise assume coopmission
|
|
}
|
|
else if ( CSGameRules() )
|
|
{ // keeping this around for local compatibility testing with listenservers
|
|
bGuardian = CSGameRules()->IsPlayingCoopGuardian();
|
|
bCoopMission = CSGameRules()->IsPlayingCoopMission();
|
|
// otherwise assume coopmission
|
|
}
|
|
else
|
|
return 0.0f;
|
|
|
|
int32 nResult = 0;
|
|
|
|
//
|
|
// Base scorechart
|
|
//
|
|
if ( !bBonus )
|
|
{
|
|
|
|
if ( bGuardian )
|
|
{
|
|
if ( nIndex == 0 )
|
|
{ // Damage to enemies ratio
|
|
uint32 numDmgInflicted = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_HpDmgInflicted, sld.accountentries() );
|
|
uint32 numDmgSuffered = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_HpDmgSuffered, sld.accountentries() );
|
|
if ( !numDmgInflicted )
|
|
nResult = 0;
|
|
else if ( numDmgSuffered )
|
|
{
|
|
nResult = int( 10000.0f * float( numDmgInflicted ) / float( numDmgInflicted + numDmgSuffered ) );
|
|
if ( numDmgInflicted && ( nResult <= 0 ) )
|
|
nResult = 1;
|
|
}
|
|
else
|
|
nResult = 10000;
|
|
//if ( bAsScore )
|
|
//{
|
|
// nResult = nResult;
|
|
//}
|
|
}
|
|
else if ( nIndex == 3 )
|
|
{ // Rounds failed penalty
|
|
nResult = Helper_ScoreLeaderboardData_FindEntryValue( k_EScoreLeaderboardDataEntryTag_RoundsPlayed, sld.matchentries() );
|
|
nResult = ( nResult > 1 ) ? ( nResult - 1 ) : 0;
|
|
if ( bAsScore )
|
|
{
|
|
nResult = COOPMISSION_SCORE_MULTIPLIER_ROUNDSFAILED*nResult;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( nIndex == 0 )
|
|
{ // Time Remaining on the clock
|
|
nResult = Helper_ScoreLeaderboardData_FindEntryValue( k_EScoreLeaderboardDataEntryTag_TimeRemaining, sld.matchentries() );
|
|
if ( bAsScore )
|
|
{
|
|
nResult = COOPMISSION_SCORE_MULTIPLIER_TIMELEFT*nResult;
|
|
}
|
|
}
|
|
else if ( nIndex == 3 )
|
|
{ // Total damage taken
|
|
nResult = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_HpDmgSuffered, sld.accountentries() );
|
|
if ( bAsScore )
|
|
{
|
|
nResult = COOPMISSION_SCORE_MULTIPLIER_DAMTAKEN*nResult;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bGuardian || bCoopMission )
|
|
{ // Shared categories for Guardian and Coop Mission
|
|
if ( nIndex == 1 )
|
|
{ // Bullets Accuracy
|
|
uint32 numBulletsFired = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_ShotsFired, sld.accountentries() );
|
|
uint32 numBulletsOnTarget = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_ShotsOnTarget, sld.accountentries() );
|
|
if ( !numBulletsFired )
|
|
nResult = 0;
|
|
else if ( numBulletsFired > 0 && numBulletsOnTarget < numBulletsFired )
|
|
{
|
|
nResult = int( 10000.0f * float( numBulletsOnTarget ) / float( numBulletsFired ) );
|
|
if ( numBulletsOnTarget && ( nResult <= 0 ) )
|
|
nResult = 1;
|
|
}
|
|
else
|
|
nResult = 10000;
|
|
//if ( bAsScore )
|
|
//{
|
|
// nResult = nResult;
|
|
//}
|
|
}
|
|
else if ( nIndex == 2 )
|
|
{ // Headshots kill percentage
|
|
uint32 numHeadshots = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_Headshots, sld.accountentries() );
|
|
uint32 numKills = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_Kills, sld.accountentries() );
|
|
if ( !numKills )
|
|
nResult = 0;
|
|
else if ( numKills > 0 && numHeadshots < numKills )
|
|
{
|
|
nResult = int( 10000.0f * float( numHeadshots ) / float( numKills ) );
|
|
if ( numHeadshots && ( nResult <= 0 ) )
|
|
nResult = 1;
|
|
}
|
|
else
|
|
nResult = 10000;
|
|
//if ( bAsScore )
|
|
//{
|
|
// nResult = nResult;
|
|
//}
|
|
}
|
|
}
|
|
|
|
}
|
|
//
|
|
// Scorechart for bonuses!
|
|
//
|
|
else
|
|
{
|
|
|
|
if ( bGuardian )
|
|
{
|
|
if ( nIndex == 0 )
|
|
{ // Under 3 rounds?
|
|
uint32 numRoundsPlayed = Helper_ScoreLeaderboardData_FindEntryValue( k_EScoreLeaderboardDataEntryTag_RoundsPlayed, sld.matchentries() );
|
|
nResult = numRoundsPlayed;
|
|
if ( bAsScore )
|
|
{
|
|
nResult = ( numRoundsPlayed <= 3 ) ? 5000 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bCoopMission )
|
|
{
|
|
if ( nIndex == 0 )
|
|
{ // No Deaths?
|
|
uint32 numDeaths = Helper_ScoreLeaderboardData_FindEntryValueSum( k_EScoreLeaderboardDataEntryTag_Deaths, sld.accountentries() );
|
|
nResult = numDeaths;
|
|
if ( bAsScore )
|
|
{
|
|
nResult = ( numDeaths == 0 ) ? 5000 : 0;
|
|
}
|
|
}
|
|
else if ( nIndex == 1 )
|
|
{ // All Challenge Coins?
|
|
uint32 numChallenge = Helper_ScoreLeaderboardData_FindEntryValue( k_EScoreLeaderboardDataEntryTag_BonusChallenge, sld.matchentries() );
|
|
nResult = numChallenge;
|
|
if ( bAsScore )
|
|
{
|
|
nResult = ( numChallenge ) ? 5000 : 0;
|
|
}
|
|
}
|
|
else if ( nIndex == 2 )
|
|
{ // Pistols Only?
|
|
uint32 numPistolsOnly = Helper_ScoreLeaderboardData_FindEntryValue( k_EScoreLeaderboardDataEntryTag_BonusPistolOnly, sld.matchentries() );
|
|
nResult = numPistolsOnly;
|
|
if ( bAsScore )
|
|
{
|
|
nResult = ( numPistolsOnly ) ? 10000 : 0;
|
|
}
|
|
}
|
|
else if ( nIndex == 3 )
|
|
{ // Hard Mode?
|
|
uint32 numHardMode = Helper_ScoreLeaderboardData_FindEntryValue( k_EScoreLeaderboardDataEntryTag_BonusHardMode, sld.matchentries() );
|
|
nResult = numHardMode;
|
|
if ( bAsScore )
|
|
{
|
|
nResult = ( numHardMode ) ? 25000 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return nResult;
|
|
}
|
|
|
|
|
|
|
|
#ifndef CLIENT_DLL
|
|
ConVar mp_backup_round_auto( "mp_backup_round_auto", "1", FCVAR_RELEASE, "If enabled will keep in-memory backups to handle reconnecting players even if the backup files aren't written to disk" );
|
|
ConVar mp_backup_round_file( "mp_backup_round_file", "backup", FCVAR_RELEASE, "If set then server will save all played rounds information to files filename_date_time_team1_team2_mapname_roundnum_score1_score2.txt" );
|
|
ConVar mp_backup_round_file_pattern( "mp_backup_round_file_pattern", "%prefix%_round%round%.txt", FCVAR_RELEASE, "If set then server will save all played rounds information to files named by this pattern, e.g.'%prefix%_%date%_%time%_%team1%_%team2%_%map%_round%round%_score_%score1%_%score2%.txt'" );
|
|
ConVar mp_backup_round_file_last( "mp_backup_round_file_last", "", FCVAR_RELEASE, "Every time a backup file is written the value of this convar gets updated to hold the name of the backup file." );
|
|
|
|
static bool Helper_mp_backup_round_IsEnabled()
|
|
{
|
|
if ( g_pGameTypes->GetCurrentGameType() == CS_GameType_GunGame )
|
|
{
|
|
switch ( g_pGameTypes->GetCurrentGameMode() )
|
|
{
|
|
case CS_GameMode::GunGame_Progressive:
|
|
case CS_GameMode::GunGame_Bomb:
|
|
return !!*mp_backup_round_file.GetString(); // round backups aren't supported in 'auto' mode in ArmsRace/Demolition, but we'll honor server config to write backup file
|
|
}
|
|
}
|
|
return mp_backup_round_auto.GetBool() || *mp_backup_round_file.GetString();
|
|
}
|
|
|
|
static int Helper_mp_backup_restore_from_file_sorter( char const * const *a, char const * const *b )
|
|
{
|
|
return -Q_stricmp( *a, *b ); // sort more recent files earlier
|
|
}
|
|
|
|
|
|
static void Helper_FilenameTokenReplace( char* pchBufFilename, int nSize, const char *aReplacePattern, const char *theActualValue )
|
|
{
|
|
char const *pchReplacePattern, *pchActualValue;
|
|
|
|
char const *pchEndFilename = ( char * ) pchBufFilename + nSize;
|
|
|
|
pchReplacePattern = aReplacePattern;
|
|
pchActualValue = theActualValue;
|
|
|
|
while ( char *pchReplace = Q_strstr( pchBufFilename, pchReplacePattern ) )
|
|
{
|
|
int lenReplace = Q_strlen( pchReplacePattern ), lenValue = Q_strlen( pchActualValue );
|
|
|
|
if ( pchReplace + lenValue >= pchEndFilename )
|
|
{
|
|
break;
|
|
}
|
|
|
|
Q_memmove( pchReplace + lenValue, pchReplace + lenReplace, pchEndFilename - pchReplace - MAX( lenReplace, lenValue ) );
|
|
Q_memmove( pchReplace, pchActualValue, lenValue );
|
|
|
|
}
|
|
}
|
|
|
|
static bool Helper_ShouldBroadcastCoopScoreLeaderboardData()
|
|
{
|
|
if ( !CSGameRules() )
|
|
return false;
|
|
if ( CSGameRules()->IsPlayingCoopGuardian() || CSGameRules()->IsPlayingCoopMission() )
|
|
{
|
|
// Check if the humans won the match
|
|
int numHumanRoundsWon = CSGameRules()->IsPlayingCoopMission()
|
|
? CSGameRules()->m_match.GetCTScore()
|
|
: ( CSGameRules()->IsHostageRescueMap()
|
|
? CSGameRules()->m_match.GetTerroristScore()
|
|
: CSGameRules()->m_match.GetCTScore()
|
|
);
|
|
if ( numHumanRoundsWon > 0 )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void Helper_FillScoreLeaderboardData( ScoreLeaderboardData &sld )
|
|
{
|
|
//
|
|
// This function is used in both official and community server build
|
|
// In official build it will deliver the leaderboard data to GC
|
|
// in both official and community build this data is also replicated to clients for the end of match scoreboard
|
|
//
|
|
|
|
if ( !Helper_ShouldBroadcastCoopScoreLeaderboardData() )
|
|
return;
|
|
|
|
//
|
|
// Per player stats
|
|
//
|
|
FOR_EACH_MAP( CSGameRules()->m_mapQueuedMatchmakingPlayersData, i )
|
|
{
|
|
CCSGameRules::CQMMPlayerData_t const &qmm = *CSGameRules()->m_mapQueuedMatchmakingPlayersData.Element( i );
|
|
ScoreLeaderboardData_AccountEntries *pAcc = sld.add_accountentries();
|
|
pAcc->set_accountid( qmm.m_uiPlayerAccountId );
|
|
if ( int n = qmm.m_numEnemyKills )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_Kills );
|
|
pEnt->set_val( n );
|
|
}
|
|
if ( int n = qmm.m_numEnemyKillHeadshots )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_Headshots );
|
|
pEnt->set_val( n );
|
|
}
|
|
if ( int n = qmm.m_numDeaths )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_Deaths );
|
|
pEnt->set_val( n );
|
|
}
|
|
if ( int n = qmm.m_numHealthPointsRemovedTotal )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_HpDmgSuffered );
|
|
pEnt->set_val( n );
|
|
}
|
|
if ( int n = qmm.m_numHealthPointsDealtTotal )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_HpDmgInflicted );
|
|
pEnt->set_val( n );
|
|
}
|
|
if ( int n = qmm.m_numShotsFiredTotal )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_ShotsFired );
|
|
pEnt->set_val( n );
|
|
}
|
|
if ( int n = qmm.m_numShotsOnTargetTotal )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = pAcc->add_entries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_ShotsOnTarget );
|
|
pEnt->set_val( n );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Match stats
|
|
//
|
|
if ( int n = CSGameRules()->GetTotalRoundsPlayed() )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = sld.add_matchentries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_RoundsPlayed );
|
|
pEnt->set_val( n );
|
|
}
|
|
|
|
if ( CSGameRules()->IsPlayingCoopMission() )
|
|
{
|
|
int nRemainingTime = CSGameRules()->GetRoundRemainingTime();
|
|
if ( nRemainingTime < 0 )
|
|
nRemainingTime = 0;
|
|
if ( int n = nRemainingTime )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = sld.add_matchentries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_TimeRemaining );
|
|
pEnt->set_val( n );
|
|
}
|
|
|
|
static ConVarRef mp_coopmission_bot_difficulty_offset( "mp_coopmission_bot_difficulty_offset" );
|
|
int nHardMode = ( mp_coopmission_bot_difficulty_offset.GetInt() >= 3 ) ? 1 : 0;
|
|
if ( int n = nHardMode )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = sld.add_matchentries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_BonusHardMode );
|
|
pEnt->set_val( n );
|
|
}
|
|
|
|
if ( int n = CSGameRules()->m_coopBonusPistolsOnly ? 1 : 0 )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = sld.add_matchentries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_BonusPistolOnly );
|
|
pEnt->set_val( n );
|
|
}
|
|
|
|
if ( int n = ( CSGameRules()->m_coopBonusCoinsFound == 3 ) ? 1 : 0 )
|
|
{
|
|
ScoreLeaderboardData_Entry *pEnt = sld.add_matchentries();
|
|
pEnt->set_tag( k_EScoreLeaderboardDataEntryTag_BonusChallenge );
|
|
pEnt->set_val( n );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Set the final score and the questid if applicable
|
|
//
|
|
int32 nTotalScore = 0;
|
|
for ( int nBonus = 0; nBonus <= 1; ++ nBonus )
|
|
{
|
|
int32 nScoreTier = 0;
|
|
for ( int iCategory = 0; iCategory < 5; ++ iCategory )
|
|
{
|
|
nScoreTier += CoopScoreGetRatingEntryFromLeaderboardData( sld, !!nBonus, iCategory, true );
|
|
}
|
|
if ( nScoreTier < 0 )
|
|
nScoreTier = 0;
|
|
nTotalScore += nScoreTier;
|
|
}
|
|
sld.set_score( nTotalScore );
|
|
}
|
|
|
|
bool IsAssassinationQuest( const CEconQuestDefinition *pQuest )
|
|
{
|
|
if ( pQuest &&
|
|
( V_stristr( pQuest->GetQuestExpression(), "act_kill_target" ) ||
|
|
V_stristr( pQuest->GetQuestExpression(), "act_pick_up_trophy" ) ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsAssassinationQuest( uint32 questID )
|
|
{
|
|
const CEconQuestDefinition *pQuest = GetItemSchema()->GetQuestDefinition( questID );
|
|
return IsAssassinationQuest( pQuest );
|
|
}
|
|
|
|
// Checks basic conditions for a quest (mapgroup, mode, etc) to see if a quest is possible to complete
|
|
bool Helper_CheckQuestMapAndMode( const CEconQuestDefinition *pQuest )
|
|
{
|
|
const char *szMapName = NULL;
|
|
const char *szMapGroupName = NULL;
|
|
#if defined ( CLIENT_DLL )
|
|
szMapName = engine->GetLevelNameShort();
|
|
szMapGroupName = engine->GetMapGroupName();
|
|
#else
|
|
szMapName = V_UnqualifiedFileName( STRING( gpGlobals->mapname ) );
|
|
szMapGroupName = STRING( gpGlobals->mapGroupName );
|
|
#endif
|
|
// Wrong map
|
|
if ( !StringIsEmpty( pQuest->GetMap() ) && V_strcmp( szMapName, pQuest->GetMap() ) )
|
|
return false;
|
|
|
|
// Unless the map group is named after our map (so queued for a single map) also confirm we're using the right map group
|
|
if ( V_strcmp( szMapGroupName, CFmtStr( "mg_%s", szMapName ) ) )
|
|
{
|
|
if ( !StringIsEmpty( pQuest->GetMapGroup() ) && V_strcmp( szMapGroupName, pQuest->GetMapGroup() ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const char *szCurrentModeAsString = g_pGameTypes->GetGameModeFromInt( g_pGameTypes->GetCurrentGameType(), g_pGameTypes->GetCurrentGameMode() );
|
|
// Mode doesn't match
|
|
if ( V_strcmp( pQuest->GetGameMode(), szCurrentModeAsString ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool IsAssassinationQuestActive( const CEconQuestDefinition *pQuest )
|
|
{
|
|
if ( CSGameRules() && CSGameRules()->IsWarmupPeriod() )
|
|
return false;
|
|
|
|
// We need to have an active quest with the 'act_kill_target' requirement
|
|
if ( !pQuest || !IsAssassinationQuest( pQuest ) )
|
|
return false;
|
|
|
|
// Validate target team
|
|
if ( pQuest->GetTargetTeam() != TEAM_TERRORIST && pQuest->GetTargetTeam() != TEAM_CT )
|
|
return false;
|
|
|
|
if ( !Helper_CheckQuestMapAndMode( pQuest ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
#if BACKUPSUPPORTZEROZERO
|
|
static char const * const g_szRoundBackupZeroZero = "0:0";
|
|
static char const * const g_szRoundBackupZeroZeroFileName = "00restart";
|
|
#endif
|
|
class CBackupFilesEnumerator : public CUtlVector< char const * >
|
|
{
|
|
public:
|
|
CBackupFilesEnumerator()
|
|
{
|
|
if ( !mp_backup_round_file.GetString()[0] )
|
|
{
|
|
Warning( "Current backup file prefix is not set, use mp_backup_round_file to set it.\n" );
|
|
return;
|
|
}
|
|
|
|
Warning( "Listing backup files with prefix: %s\n", mp_backup_round_file.GetString() );
|
|
|
|
FileFindHandle_t hFind = NULL;
|
|
|
|
CUtlBuffer bufFilename;
|
|
|
|
int nBufSize = 1024;
|
|
|
|
bufFilename.EnsureCapacity( nBufSize );
|
|
char *pchBufFilename = ( char * ) bufFilename.Base();
|
|
|
|
Q_memset( pchBufFilename, 0, nBufSize );
|
|
Q_snprintf( pchBufFilename, nBufSize, "%s", mp_backup_round_file_pattern.GetString() );
|
|
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%prefix%", mp_backup_round_file.GetString() );
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%date%", "*" );
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%time%", "*" );
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%round%", "*" );
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%score1%", "*" );
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%score2%", "*" );
|
|
|
|
|
|
char chTeam1[64] = {0};
|
|
Q_snprintf( chTeam1, sizeof( chTeam1 ), "%s", GetGlobalTeam( CSGameRules()->AreTeamsPlayingSwitchedSides() ? TEAM_TERRORIST : TEAM_CT )->GetClanName() );
|
|
for ( char *pch = chTeam1; *pch; ++ pch )
|
|
{
|
|
if (
|
|
( ( pch[0] >= 'a' ) && ( pch[0] <= 'z' ) ) ||
|
|
( ( pch[0] >= 'A' ) && ( pch[0] <= 'Z' ) ) ||
|
|
( ( pch[0] >= '0' ) && ( pch[0] <= '9' ) )
|
|
)
|
|
;
|
|
else
|
|
{
|
|
Q_memmove( pch, pch + 1, sizeof( chTeam1 ) - ( pch - chTeam1 ) - 1 );
|
|
-- pch;
|
|
}
|
|
}
|
|
char chTeam2[64] = {0};
|
|
Q_snprintf( chTeam2, sizeof( chTeam2 ), "%s", GetGlobalTeam( CSGameRules()->AreTeamsPlayingSwitchedSides() ? TEAM_CT : TEAM_TERRORIST )->GetClanName() );
|
|
for ( char *pch = chTeam2; *pch; ++ pch )
|
|
{
|
|
if (
|
|
( ( pch[0] >= 'a' ) && ( pch[0] <= 'z' ) ) ||
|
|
( ( pch[0] >= 'A' ) && ( pch[0] <= 'Z' ) ) ||
|
|
( ( pch[0] >= '0' ) && ( pch[0] <= '9' ) )
|
|
)
|
|
;
|
|
else
|
|
{
|
|
Q_memmove( pch, pch + 1, sizeof( chTeam2 ) - ( pch - chTeam2 ) - 1 );
|
|
-- pch;
|
|
}
|
|
}
|
|
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%team1%", chTeam1 );
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%team2%", chTeam2 );
|
|
{ // Map might have a path in it, make sure we strip it
|
|
const char *szMapNameToken = STRING( gpGlobals->mapname );
|
|
if ( const char *pchSlash = strrchr( szMapNameToken, '/' ) )
|
|
szMapNameToken = pchSlash + 1;
|
|
if ( const char *pchSlash2 = strrchr( szMapNameToken, '\\' ) )
|
|
szMapNameToken = pchSlash2 + 1;
|
|
Helper_FilenameTokenReplace( pchBufFilename, nBufSize, "%map%", szMapNameToken );
|
|
}
|
|
|
|
for ( char const *szFileName = filesystem->FindFirst( pchBufFilename, &hFind );
|
|
szFileName && *szFileName; szFileName = filesystem->FindNext( hFind ) )
|
|
{
|
|
m_mapFiles.AddString( szFileName );
|
|
}
|
|
|
|
filesystem->FindClose( hFind );
|
|
|
|
for ( int iStr = 0; iStr < m_mapFiles.GetNumStrings(); ++ iStr )
|
|
{
|
|
AddToTail( m_mapFiles.String( iStr ) );
|
|
}
|
|
|
|
Sort( Helper_mp_backup_restore_from_file_sorter );
|
|
|
|
#if BACKUPSUPPORTZEROZERO
|
|
// add 0:0 backup
|
|
static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
|
|
if ( s_pchTournamentServer )
|
|
AddToTail( g_szRoundBackupZeroZeroFileName );
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
CUtlStringMap< bool > m_mapFiles;
|
|
};
|
|
|
|
|
|
#if defined( _DEBUG )
|
|
#define ROUND_BACKUP_REQUEST_RATE_LIMIT 5
|
|
#else
|
|
#define ROUND_BACKUP_REQUEST_RATE_LIMIT 10
|
|
#endif
|
|
|
|
// concommand that requests a user message with the round backup filenames in it.
|
|
CON_COMMAND_F ( send_round_backup_file_list, "", FCVAR_GAMEDLL | FCVAR_RELEASE |FCVAR_HIDDEN|FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS )
|
|
{
|
|
|
|
CBasePlayer* pPlayer = UTIL_GetCommandClient();
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
static CUtlMap<int, int > s_mapLastRequestTime;
|
|
if ( s_mapLastRequestTime.Count() == 0 )
|
|
{
|
|
s_mapLastRequestTime.SetLessFunc( DefLessFunc( int ) );
|
|
}
|
|
|
|
// prevent spamming of the command. store each user's previous request time.
|
|
|
|
int nIndex = s_mapLastRequestTime.Find( pPlayer->entindex() );
|
|
|
|
if ( ( s_mapLastRequestTime.IsValidIndex( nIndex ) ) &&
|
|
( gpGlobals->curtime > s_mapLastRequestTime[ nIndex ] ) &&
|
|
( ( gpGlobals->curtime - s_mapLastRequestTime[ nIndex ] ) <= ROUND_BACKUP_REQUEST_RATE_LIMIT ) )
|
|
return;
|
|
|
|
s_mapLastRequestTime.InsertOrReplace( pPlayer->entindex(), ( int )gpGlobals->curtime );
|
|
|
|
CCSUsrMsg_RoundBackupFilenames msg;
|
|
|
|
CBackupFilesEnumerator arrStrings;
|
|
|
|
// to avoid the proto message limit we're sending these in individual messages rather than one.
|
|
for ( int idx = 0; idx < Min( 10, arrStrings.Count() ); idx++ )
|
|
{
|
|
CCSUsrMsg_RoundBackupFilenames msg;
|
|
|
|
msg.set_count( Min( 10, arrStrings.Count() ) );
|
|
|
|
msg.set_index( idx );
|
|
|
|
msg.set_filename( arrStrings[ idx ] );
|
|
|
|
// create human readable name
|
|
KeyValues *kvSaveFile = new KeyValues( "" );
|
|
KeyValues::AutoDelete autodelete_kvSaveFile( kvSaveFile );
|
|
autodelete_kvSaveFile->UsesEscapeSequences( true );
|
|
|
|
CUtlString nicefilename = arrStrings[ idx ];
|
|
|
|
#if BACKUPSUPPORTZEROZERO
|
|
if ( !V_strcmp( arrStrings[ idx ], g_szRoundBackupZeroZeroFileName ) )
|
|
{
|
|
nicefilename = g_szRoundBackupZeroZero;
|
|
}
|
|
else
|
|
#endif
|
|
if ( kvSaveFile->LoadFromFile( filesystem, arrStrings[ idx ] ) )
|
|
{
|
|
nicefilename = CFmtStr( "%s score %d:%d",
|
|
kvSaveFile->GetString( "timestamp" ),
|
|
kvSaveFile->GetInt( "FirstHalfScore/team1" ) + kvSaveFile->GetInt( "SecondHalfScore/team1" ) + kvSaveFile->GetInt( "OvertimeScore/team1" ),
|
|
kvSaveFile->GetInt( "FirstHalfScore/team2" ) + kvSaveFile->GetInt( "SecondHalfScore/team2" ) + kvSaveFile->GetInt( "OvertimeScore/team2" ));
|
|
}
|
|
|
|
msg.set_nicename( nicefilename );
|
|
|
|
CSingleUserRecipientFilter filter( pPlayer );
|
|
|
|
SendUserMessage( filter, CS_UM_RoundBackupFilenames, msg );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
CON_COMMAND_F ( mp_backup_restore_list_files, "Lists recent backup round files matching the prefix, most recent files first, accepts a numeric parameter to limit the number of files displayed", FCVAR_GAMEDLL | FCVAR_RELEASE )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
int numFilesToList = 5;
|
|
if ( args.ArgC() >= 2 )
|
|
{
|
|
numFilesToList = Q_atoi( args.Arg( 1 ) );
|
|
}
|
|
|
|
CBackupFilesEnumerator arrStrings;
|
|
|
|
FOR_EACH_VEC( arrStrings, idxString )
|
|
{
|
|
if ( idxString >= numFilesToList )
|
|
break;
|
|
Warning( " %s\n", arrStrings[idxString] );
|
|
}
|
|
if ( !arrStrings.Count() )
|
|
{
|
|
Warning( "No matching backup files found.\n" );
|
|
}
|
|
else if ( numFilesToList < arrStrings.Count() )
|
|
{
|
|
Warning( "%d backup files found, %d most recent listed, use 'mp_backup_restore_list_files %d' to list all.\n", arrStrings.Count(), numFilesToList, arrStrings.Count() );
|
|
}
|
|
else
|
|
{
|
|
Warning( "%d backup files listed.\n", arrStrings.Count() );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
ConVar mp_backup_restore_load_autopause( "mp_backup_restore_load_autopause", "1", FCVAR_RELEASE, "Whether to automatically pause the match after restoring round data from backup" );
|
|
CON_COMMAND_F ( mp_backup_restore_load_file, "Loads player cash, KDA, scores and team scores; resets to the next round after the backup", FCVAR_GAMEDLL | FCVAR_RELEASE )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Warning( "Usage: mp_backup_restore_load_file [filename], use 'mp_backup_restore_list_files' to list matching backup files.\n" );
|
|
return;
|
|
}
|
|
CSGameRules()->LoadRoundDataInformation( args.Arg( 1 ) );
|
|
}
|
|
|
|
CMsgGCCStrike15_v2_MatchmakingGC2ServerReserve CCSGameRules::sm_QueuedServerReservation;
|
|
#endif
|
|
|
|
#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
|
|
|
|
|
|
// MOSTLY DECPRECATED.
|
|
// All weapons ( except exhaustibles, ITEM_FLAG_EXHAUSTIBLE, such as grenades ) store their reserve ammo on the weapon entity
|
|
// and no longer on the player. The reserve amount is specified in schema as attributes "primary reserve ammo max" and "secondary reserve ammo max"
|
|
|
|
ConVar ammo_50AE_max( "ammo_50AE_max", "35", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_762mm_max( "ammo_762mm_max", "90", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_556mm_max( "ammo_556mm_max", "90", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_556mm_small_max( "ammo_556mm_small_max", "40", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_556mm_box_max( "ammo_556mm_box_max", "200", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_338mag_max( "ammo_338mag_max", "30", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_9mm_max( "ammo_9mm_max", "120", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_buckshot_max( "ammo_buckshot_max", "32", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_45acp_max( "ammo_45acp_max", "100", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_357sig_max( "ammo_357sig_max", "52", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_357sig_p250_max( "ammo_357sig_p250_max", "26", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_357sig_small_max( "ammo_357sig_small_max", "24", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_357sig_min_max( "ammo_357sig_min_max", "12", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_57mm_max( "ammo_57mm_max", "100", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
|
|
ConVar ammo_grenade_limit_default( "ammo_grenade_limit_default", "1", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_grenade_limit_flashbang( "ammo_grenade_limit_flashbang", "1", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
ConVar ammo_grenade_limit_total( "ammo_grenade_limit_total", "3", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
|
|
ConVar ammo_item_limit_healthshot( "ammo_item_limit_healthshot", "4", FCVAR_REPLICATED | FCVAR_RELEASE );
|
|
|
|
ConVar ammo_50AE_impulse( "ammo_50AE_impulse", "2400", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_762mm_impulse( "ammo_762mm_impulse", "2400", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_556mm_impulse( "ammo_556mm_impulse", "2400", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_556mm_box_impulse( "ammo_556mm_box_impulse", "2400", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_338mag_impulse( "ammo_338mag_impulse", "2800", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_9mm_impulse( "ammo_9mm_impulse", "2000", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_buckshot_impulse( "ammo_buckshot_impulse", "600", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_45acp_impulse( "ammo_45acp_impulse", "2100", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_357sig_impulse( "ammo_357sig_impulse", "2000", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_57mm_impulse( "ammo_57mm_impulse", "2000", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
|
|
ConVar ammo_50AE_headshot_mult( "ammo_50AE_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_762mm_headshot_mult( "ammo_762mm_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_556mm_headshot_mult( "ammo_556mm_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_556mm_box_headshot_mult( "ammo_556mm_box_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_338mag_headshot_mult( "ammo_338mag_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_9mm_headshot_mult( "ammo_9mm_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_buckshot_headshot_mult( "ammo_buckshot_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_45acp_headshot_mult( "ammo_45acp_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_357sig_headshot_mult( "ammo_357sig_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
ConVar ammo_57mm_headshot_mult( "ammo_57mm_headshot_mult", "1.0", FCVAR_REPLICATED, "You must enable tweaking via tweak_ammo_impulses to use this value." );
|
|
|
|
//ConVar mp_dynamicpricing( "mp_dynamicpricing", "0", FCVAR_REPLICATED, "Enables or Disables the dynamic weapon prices" );
|
|
|
|
ConVar mp_spec_swapplayersides(
|
|
"mp_spec_swapplayersides",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Toggle set the player names and team names to the opposite side in which they are are on the spectator panel.",
|
|
true, 0,
|
|
true, 1
|
|
);
|
|
|
|
ConVar mp_force_assign_teams(
|
|
"mp_force_assign_teams",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Players don't get to choose what team they are on, it is auto assinged.",
|
|
true, 0,
|
|
true, 1
|
|
);
|
|
|
|
#if defined( GAME_DLL )
|
|
// need this defined for the server so it can load from game.cfg
|
|
ConVar sv_gameinstructor_disable( "sv_gameinstructor_disable", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Force all clients to disable their game instructors." );
|
|
|
|
ConVar cs_AssistDamageThreshold( "cs_AssistDamageThreshold", "40.0", FCVAR_DEVELOPMENTONLY, "cs_AssistDamageThreshold defines the amount of damage needed to score an assist" );
|
|
#endif
|
|
|
|
ConVar sv_matchpause_auto_5v5( "sv_matchpause_auto_5v5", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "When enabled will automatically pause the match at next freeze time if less than 5 players are connected on each team." );
|
|
|
|
|
|
extern ConVar sv_stopspeed;
|
|
extern ConVar mp_randomspawn;
|
|
extern ConVar mp_randomspawn_los;
|
|
extern ConVar mp_teammates_are_enemies;
|
|
extern ConVar mp_respawnwavetime;
|
|
extern ConVar mp_hostages_max;
|
|
extern ConVar mp_hostages_spawn_farthest;
|
|
extern ConVar mp_hostages_spawn_force_positions;
|
|
extern ConVar mp_hostages_spawn_same_every_round;
|
|
extern ConVar weapon_max_before_cleanup;
|
|
|
|
ConVar mp_spectators_max(
|
|
"mp_spectators_max",
|
|
"2",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"How many spectators are allowed in a match.",
|
|
true, 0,
|
|
false, 0 );
|
|
|
|
ConVar mp_buytime(
|
|
"mp_buytime",
|
|
"90",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"How many seconds after round start players can buy items for.",
|
|
true, 0,
|
|
false, 0 );
|
|
|
|
ConVar mp_buy_allow_grenades(
|
|
"mp_buy_allow_grenades",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether players can purchase grenades from the buy menu or not.",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_do_warmup_period(
|
|
"mp_do_warmup_period",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether or not to do a warmup period at the start of a match.",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_do_warmup_offine(
|
|
"mp_do_warmup_offine",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether or not to do a warmup period at the start of a match in an offline (bot) match.",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_startmoney(
|
|
"mp_startmoney",
|
|
"800",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"amount of money each player gets when they reset",
|
|
true, 0,
|
|
false, 0 );
|
|
|
|
ConVar mp_maxmoney(
|
|
"mp_maxmoney",
|
|
"16000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"maximum amount of money allowed in a player's account",
|
|
true, 0,
|
|
false, 0 );
|
|
|
|
ConVar mp_afterroundmoney(
|
|
"mp_afterroundmoney",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"amount of money awared to every player after each round" );
|
|
|
|
ConVar mp_playercashawards(
|
|
"mp_playercashawards",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Players can earn money by performing in-game actions" );
|
|
|
|
ConVar mp_teamcashawards(
|
|
"mp_teamcashawards",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Teams can earn money by performing in-game actions" );
|
|
|
|
ConVar mp_overtime_enable(
|
|
"mp_overtime_enable",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If a match ends in a tie, use overtime rules to determine winner" );
|
|
|
|
ConVar mp_overtime_maxrounds(
|
|
"mp_overtime_maxrounds",
|
|
"6",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"When overtime is enabled play additional rounds to determine winner" );
|
|
|
|
ConVar mp_overtime_startmoney(
|
|
"mp_overtime_startmoney",
|
|
"10000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Money assigned to all players at start of every overtime half" );
|
|
|
|
ConVar mp_hostages_takedamage(
|
|
"mp_hostages_takedamage",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether or not hostages can be hurt." );
|
|
|
|
ConVar mp_hostages_rescuetowin(
|
|
"mp_hostages_rescuetowin",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"0 == all alive, any other number is the number the CT's need to rescue to win the round." );
|
|
|
|
ConVar mp_hostages_rescuetime(
|
|
"mp_hostages_rescuetime",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Additional time added to round time if a hostage is reached by a CT." );
|
|
|
|
ConVar mp_anyone_can_pickup_c4(
|
|
"mp_anyone_can_pickup_c4",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set, everyone can pick up the c4, not just Ts." );
|
|
|
|
ConVar mp_c4_cannot_be_defused(
|
|
"mp_c4_cannot_be_defused",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set, the planted c4 cannot be defused." );
|
|
|
|
ConVar sv_coaching_enabled(
|
|
"sv_coaching_enabled",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Allows spectating and communicating with a team ( 'coach t' or 'coach ct' )" );
|
|
|
|
ConVar sv_allow_thirdperson(
|
|
"sv_allow_thirdperson",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Allows the server set players in third person mode without the client slamming it back (if cheats are on, all clients can set thirdperson without this convar being set)" );
|
|
|
|
ConVar sv_party_mode(
|
|
"sv_party_mode",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Party!!" );
|
|
|
|
// ConVar mp_hostages_canpickup(
|
|
// "mp_hostages_canpickup",
|
|
// "1",
|
|
// FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
// "1 = hostages are picked up when used, 0 == hostages uyse the old method of following behind players." );
|
|
|
|
#ifndef CLIENT_DLL
|
|
CON_COMMAND( timeout_terrorist_start, "" )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
CSGameRules()->StartTerroristTimeOut();
|
|
}
|
|
|
|
CON_COMMAND( timeout_ct_start, "" )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
CSGameRules()->StartCTTimeOut();
|
|
}
|
|
CON_COMMAND( mp_warmup_start, "Start warmup." )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
if ( CSGameRules() )
|
|
{
|
|
if ( CSGameRules()->IsQueuedMatchmaking() )
|
|
{
|
|
Msg( "mp_warmup_start cannot be used on official servers, use a longer mp_warmuptime instead!\n" );
|
|
}
|
|
else
|
|
{
|
|
CSGameRules()->StartWarmup();
|
|
}
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( mp_warmup_end, "End warmup immediately." )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
{
|
|
// // Special handling here allowing players to unpause warmup timer
|
|
// // if it is in mode #2 where it is paused until getting unpaused by players
|
|
// extern ConVar mp_warmup_pausetimer;
|
|
// extern ConVar mp_warmuptime;
|
|
// if ( CSGameRules() && CSGameRules()->IsWarmupPeriod() && ( mp_warmup_pausetimer.GetInt() == 2 ) )
|
|
// {
|
|
// int nNewWarmupTime = mp_warmuptime.GetInt();
|
|
// if ( nNewWarmupTime < 15 )
|
|
// {
|
|
// mp_warmuptime.SetValue( 15 );
|
|
// }
|
|
// mp_warmup_pausetimer.SetValue( 0 );
|
|
//
|
|
// CSGameRules()->SetWarmupPeriodStartTime( gpGlobals->curtime );
|
|
// CSGameRules()->m_fWarmupNextChatNoticeTime = gpGlobals->curtime + 10;
|
|
//
|
|
// CBroadcastRecipientFilter filter;
|
|
// int issuingPlayerIndex = UTIL_GetCommandClientIndex();
|
|
// if ( CCSPlayer *pPlayer = ToCSPlayer( UTIL_EntityByIndex( issuingPlayerIndex ) ) )
|
|
// {
|
|
// Warning( "mp_warmup_end issued by player#%d(%s), unpausing warmup...\n", issuingPlayerIndex, pPlayer->GetPlayerName() );
|
|
// UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Player_Wants_Restart", pPlayer->GetPlayerName() );
|
|
// }
|
|
// else
|
|
// {
|
|
// Warning( "mp_warmup_end issued by player#%d with no player info, unpausing warmup...\n", issuingPlayerIndex );
|
|
// }
|
|
// UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_Match_Will_Start_Chat" );
|
|
// }
|
|
|
|
return;
|
|
}
|
|
|
|
if ( CSGameRules() )
|
|
{
|
|
if ( CSGameRules()->IsQueuedMatchmaking() )
|
|
{
|
|
Msg( "mp_warmup_start cannot be used on official servers, use a shorter mp_warmuptime instead!\n" );
|
|
}
|
|
else
|
|
{
|
|
CSGameRules()->EndWarmup();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void mpwarmuptime_f( IConVar *pConVar, const char *pOldString, float flOldValue )
|
|
{
|
|
if ( CSGameRules() )
|
|
{
|
|
CSGameRules()->SetWarmupPeriodStartTime( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
|
|
ConVar mp_verbose_changelevel_spew( "mp_verbose_changelevel_spew", "1", FCVAR_RELEASE );
|
|
|
|
ConVar mp_warmuptime(
|
|
"mp_warmuptime",
|
|
"30",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"How long the warmup period lasts. Changing this value resets warmup.",
|
|
true, 5,
|
|
false, 0,
|
|
mpwarmuptime_f );
|
|
|
|
ConVar mp_warmuptime_all_players_connected(
|
|
"mp_warmuptime_all_players_connected",
|
|
"60",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Warmup time to use when all players have connected in official competitive. 0 to disable." );
|
|
|
|
ConVar mp_warmup_pausetimer(
|
|
"mp_warmup_pausetimer",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Set to 1 to stay in warmup indefinitely. Set to 0 to resume the timer." );
|
|
|
|
ConVar mp_halftime_pausetimer(
|
|
"mp_halftime_pausetimer",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Set to 1 to stay in halftime indefinitely. Set to 0 to resume the timer." );
|
|
|
|
ConVar mp_halftime_pausematch(
|
|
"mp_halftime_pausematch",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Set to 1 to pause match after halftime countdown elapses. Match must be resumed by vote or admin." );
|
|
|
|
ConVar mp_overtime_halftime_pausetimer(
|
|
"mp_overtime_halftime_pausetimer",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set to 1 will set mp_halftime_pausetimer to 1 before every half of overtime. Set mp_halftime_pausetimer to 0 to resume the timer." );
|
|
|
|
ConVar mp_respawn_immunitytime("mp_respawn_immunitytime", "4.0", FCVAR_RELEASE | FCVAR_REPLICATED, "How many seconds after respawn immunity lasts." );
|
|
|
|
ConVar mp_playerid(
|
|
"mp_playerid",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"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.4",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds to delay showing information in the status bar",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_playerid_hold(
|
|
"mp_playerid_hold",
|
|
"0.2",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"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",
|
|
"7.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds to delay before restarting a round after a win",
|
|
true, 0.0f,
|
|
true, 14.0f ); // 10 - 11 seconds is a comfortable minimum to fit the end of round replay
|
|
|
|
ConVar mp_halftime_duration(
|
|
"mp_halftime_duration",
|
|
"15.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds that halftime lasts",
|
|
true, 0.0f,
|
|
true, 300.0f );
|
|
|
|
ConVar mp_match_can_clinch(
|
|
"mp_match_can_clinch",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Can a team clinch and end the match by being so far ahead that the other team has no way to catching up?" );
|
|
|
|
ConVar mp_ggtr_end_round_kill_bonus(
|
|
"mp_ggtr_end_round_kill_bonus",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of bonus points awarded in Demolition Mode when knife kill ends round",
|
|
true, 0,
|
|
true, 10 );
|
|
|
|
ConVar mp_ggtr_last_weapon_kill_ends_half(
|
|
"mp_ggtr_last_weapon_kill_ends_half",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"End the half and give a team round point when a player makes a kill using the final weapon",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_ggprogressive_round_restart_delay(
|
|
"mp_ggprogressive_round_restart_delay",
|
|
"15.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds to delay before restarting a round after a win in gungame progessive",
|
|
true, 0.0f,
|
|
true, 90.0f );
|
|
|
|
ConVar mp_ggprogressive_use_random_weapons(
|
|
"mp_ggprogressive_use_random_weapons",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set, selects random weapons from set categories for the progression order" );
|
|
|
|
ConVar mp_ggprogressive_random_weapon_kills_needed(
|
|
"mp_ggprogressive_random_weapon_kills_needed",
|
|
"2",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If mp_ggprogressive_use_random_weapons is set, this is the number of kills needed with each weapon" );
|
|
|
|
ConVar mp_ggtr_num_rounds_autoprogress(
|
|
"mp_ggtr_num_rounds_autoprogress",
|
|
"3",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Upgrade the player's weapon after this number of rounds without upgrading" );
|
|
|
|
ConVar mp_ct_default_melee(
|
|
"mp_ct_default_melee",
|
|
"weapon_knife",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default melee weapon that the CTs will spawn with. Even if this is blank, a knife will be given. To give a taser, it should look like this: 'weapon_knife weapon_taser'. Remember to set mp_weapons_allow_zeus to 1 if you want to give a taser!" );
|
|
|
|
ConVar mp_ct_default_secondary(
|
|
"mp_ct_default_secondary",
|
|
"weapon_hkp2000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default secondary (pistol) weapon that the CTs will spawn with" );
|
|
|
|
ConVar mp_ct_default_primary(
|
|
"mp_ct_default_primary",
|
|
"",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default primary (rifle) weapon that the CTs will spawn with" );
|
|
|
|
ConVar mp_ct_default_grenades(
|
|
"mp_ct_default_grenades",
|
|
"",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default grenades that the CTs will spawn with. To give multiple grenades, separate each weapon class with a space like this: 'weapon_molotov weapon_hegrenade'" );
|
|
|
|
ConVar mp_t_default_melee(
|
|
"mp_t_default_melee",
|
|
"weapon_knife",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default melee weapon that the Ts will spawn with" );
|
|
|
|
ConVar mp_t_default_secondary(
|
|
"mp_t_default_secondary",
|
|
"weapon_glock",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default secondary (pistol) weapon that the Ts will spawn with" );
|
|
|
|
ConVar mp_t_default_primary(
|
|
"mp_t_default_primary",
|
|
"",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default primary (rifle) weapon that the Ts will spawn with" );
|
|
|
|
ConVar mp_t_default_grenades(
|
|
"mp_t_default_grenades",
|
|
"",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The default grenades that the Ts will spawn with. To give multiple grenades, separate each weapon class with a space like this: 'weapon_molotov weapon_hegrenade'" );
|
|
|
|
ConVar mp_join_grace_time(
|
|
"mp_join_grace_time",
|
|
"0.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds after round start to allow a player to join a game",
|
|
true, 0.0f,
|
|
true, 30.0f );
|
|
|
|
ConVar mp_win_panel_display_time(
|
|
"mp_win_panel_display_time",
|
|
"3",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The amount of time to show the win panel between matches / halfs" );
|
|
|
|
ConVar mp_ggtr_bomb_pts_for_upgrade(
|
|
"mp_ggtr_bomb_pts_for_upgrade",
|
|
"2.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Kill points required to upgrade a player's weapon",
|
|
true, 1.0,
|
|
true, 10.0 );
|
|
|
|
ConVar mp_ggtr_bomb_pts_for_he(
|
|
"mp_ggtr_bomb_pts_for_he",
|
|
"3",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Kill points required in a round to get a bonus HE grenade",
|
|
true, 1,
|
|
true, 5);
|
|
|
|
ConVar mp_ggtr_bomb_pts_for_flash(
|
|
"mp_ggtr_bomb_pts_for_flash",
|
|
"4",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Kill points required in a round to get a bonus flash grenade",
|
|
true, 1,
|
|
true, 5);
|
|
|
|
ConVar mp_ggtr_bomb_pts_for_molotov(
|
|
"mp_ggtr_bomb_pts_for_molotov",
|
|
"5",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Kill points required in a round to get a bonus molotov cocktail",
|
|
true, 1,
|
|
true, 5);
|
|
|
|
ConVar mp_molotovusedelay(
|
|
"mp_molotovusedelay",
|
|
"15.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds to delay before the molotov can be used after acquiring it",
|
|
true, 0.0,
|
|
true, 30.0 );
|
|
|
|
ConVar mp_ggtr_halftime_delay(
|
|
"mp_ggtr_halftime_delay",
|
|
"0.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds to delay during TR Mode halftime",
|
|
true, 0.0,
|
|
true, 30.0 );
|
|
|
|
ConVar mp_ggtr_bomb_respawn_delay(
|
|
"mp_ggtr_bomb_respawn_delay",
|
|
"0.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of seconds to delay before making the bomb available to a respawner in gun game",
|
|
true, 0.0,
|
|
true, 30.0 );
|
|
|
|
ConVar mp_ggtr_bomb_defuse_bonus(
|
|
"mp_ggtr_bomb_defuse_bonus",
|
|
"1.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of bonus upgrades to award the CTs when they defuse a gun game bomb",
|
|
true, 1.0,
|
|
true, 10.0 );
|
|
|
|
ConVar mp_ggtr_bomb_detonation_bonus(
|
|
"mp_ggtr_bomb_detonation_bonus",
|
|
"1.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Number of bonus upgrades to award the Ts when they detonate a gun game bomb",
|
|
true, 1.0,
|
|
true, 10.0 );
|
|
|
|
ConVar mp_dm_bonus_percent(
|
|
"mp_dm_bonus_percent",
|
|
"50",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Percent of points additionally awarded when someone gets a kill with the bonus weapon during the bonus period." );
|
|
|
|
ConVar mp_display_kill_assists(
|
|
"mp_display_kill_assists",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether to display and score player assists",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_match_end_restart(
|
|
"mp_match_end_restart",
|
|
#if defined (CSTRIKE15)
|
|
"0",
|
|
#else
|
|
"0",
|
|
#endif
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"At the end of the match, perform a restart instead of loading a new map",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_match_end_changelevel(
|
|
"mp_match_end_changelevel",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"At the end of the match, perform a changelevel even if next map is the same",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_defuser_allocation(
|
|
"mp_defuser_allocation",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"How to allocate defusers to CTs at start or round: 0=none, 1=random, 2=everyone",
|
|
true, 0,
|
|
true, 2 );
|
|
|
|
ConVar mp_give_player_c4(
|
|
"mp_give_player_c4",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether this map should spawn a c4 bomb for a player or not.",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_death_drop_gun(
|
|
"mp_death_drop_gun",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Which gun to drop on player death: 0=none, 1=best, 2=current or best",
|
|
true, 0,
|
|
true, 2 );
|
|
|
|
ConVar mp_death_drop_c4(
|
|
"mp_death_drop_c4",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether c4 is droppable" );
|
|
|
|
ConVar mp_death_drop_grenade(
|
|
"mp_death_drop_grenade",
|
|
"2",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Which grenade to drop on player death: 0=none, 1=best, 2=current or best, 3=all grenades",
|
|
true, 0,
|
|
true, 3 );
|
|
|
|
ConVar mp_death_drop_defuser(
|
|
"mp_death_drop_defuser",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Drop defuser on player death",
|
|
true, 0,
|
|
true, 1 );
|
|
|
|
ConVar mp_coop_force_join_ct(
|
|
"mp_coop_force_join_ct",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set, real players will auto join CT on join." );
|
|
|
|
ConVar mp_coopmission_mission_number(
|
|
"mp_coopmission_mission_number",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Which mission the map should run after it loads." );
|
|
|
|
ConVar mp_force_pick_time(
|
|
"mp_force_pick_time",
|
|
"15",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The amount of time a player has on the team screen to make a selection before being auto-teamed" );
|
|
|
|
ConVar bot_autodifficulty_threshold_low(
|
|
"bot_autodifficulty_threshold_low",
|
|
"-2.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Lower bound below Average Human Contribution Score that a bot must be below to change its difficulty",
|
|
true, -20.0,
|
|
true, 20.0 );
|
|
|
|
ConVar bot_autodifficulty_threshold_high(
|
|
"bot_autodifficulty_threshold_high",
|
|
"5.0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Upper bound above Average Human Contribution Score that a bot must be above to change its difficulty",
|
|
true, -20.0,
|
|
true, 20.0 );
|
|
|
|
ConVar mp_weapons_allow_zeus(
|
|
"mp_weapons_allow_zeus",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Determines how many Zeus purchases a player can make per round (0 to disallow, -1 to have no limit)." );
|
|
|
|
ConVar mp_weapons_allow_typecount(
|
|
"mp_weapons_allow_typecount",
|
|
"5",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Determines how many purchases of each weapon type allowed per player per round (0 to disallow purchasing, -1 to have no limit)." );
|
|
|
|
ConVar mp_weapons_allow_map_placed(
|
|
"mp_weapons_allow_map_placed",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If this convar is set, when a match starts, the game will not delete weapons placed in the map." );
|
|
|
|
ConVar mp_default_team_winner_no_objective(
|
|
"mp_default_team_winner_no_objective",
|
|
"-1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If the map doesn't define an objective (bomb, hostage, etc), the value of this convar will declare the winner when the time runs out in the round." );
|
|
|
|
ConVar mp_weapons_glow_on_ground(
|
|
"mp_weapons_glow_on_ground",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If this convar is set, weapons on the ground will have a glow around them." );
|
|
|
|
ConVar mp_respawn_on_death_t(
|
|
"mp_respawn_on_death_t",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"When set to 1, terrorists will respawn after dying." );
|
|
|
|
ConVar mp_respawn_on_death_ct(
|
|
"mp_respawn_on_death_ct",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"When set to 1, counter-terrorists will respawn after dying." );
|
|
|
|
ConVar mp_use_respawn_waves(
|
|
"mp_use_respawn_waves",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"When set to 1, and that player's team is set to respawn, they will respawn in waves. If set to 2, teams will respawn when the whole team is dead." );
|
|
|
|
|
|
void ProhibitedItemsCallback( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
#ifdef GAME_DLL
|
|
|
|
if ( !CSGameRules() )
|
|
return;
|
|
|
|
ConVar *pCvar = static_cast<ConVar*>(var);
|
|
|
|
CUtlStringList pProhibitedWeapons( pCvar->GetString(), "," );
|
|
|
|
for( int i = 0; i < MAX_PROHIBITED_ITEMS; i++ )
|
|
{
|
|
if ( i < (pProhibitedWeapons.Count()) && ( GetItemSchema()->GetItemDefinition( i ) ) )
|
|
{
|
|
int nDefIndex = V_atoi( pProhibitedWeapons[ i ] );
|
|
|
|
CSGameRules()->m_arrProhibitedItemIndices.Set( i, nDefIndex );
|
|
DevMsg( "Prohibiting %s\n", GetItemSchema()->GetItemDefinition( nDefIndex )->GetDefinitionName() );
|
|
}
|
|
else
|
|
{
|
|
CSGameRules()->m_arrProhibitedItemIndices.Set( i, 0 );
|
|
}
|
|
}
|
|
|
|
#endif // GAME_DLL
|
|
}
|
|
|
|
ConVar mp_items_prohibited(
|
|
"mp_items_prohibited",
|
|
"",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Set this convar to a comma-delimited list of definition indices of weapons that should be prohibited from use.",
|
|
ProhibitedItemsCallback );
|
|
|
|
|
|
|
|
void RespawnWaveTimeCTCallback( IConVar *var, const char *pOldValue, float flOldValue );
|
|
void RespawnWaveTimeTCallback( IConVar *var, const char *pOldValue, float flOldValue );
|
|
|
|
void RespawnWaveTimeCTCallback( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
extern ConVar mp_respawnwavetime_ct;
|
|
float flTime = mp_respawnwavetime_ct.GetFloat();
|
|
if ( CSGameRules() )
|
|
{
|
|
float flNextRespawn = gpGlobals->curtime + flTime;
|
|
CSGameRules()->SetNextTeamRespawnWaveDelay( TEAM_CT, flNextRespawn );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void RespawnWaveTimeTCallback( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
extern ConVar mp_respawnwavetime_t;
|
|
float flTime = mp_respawnwavetime_t.GetFloat();
|
|
if ( CSGameRules() )
|
|
{
|
|
float flNextRespawn = gpGlobals->curtime + flTime;
|
|
CSGameRules()->SetNextTeamRespawnWaveDelay( TEAM_TERRORIST, flNextRespawn );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ConVar mp_respawnwavetime_ct( "mp_respawnwavetime_ct", "10.0", FCVAR_REPLICATED | FCVAR_RELEASE, "Time between respawn waves for CTs.", RespawnWaveTimeCTCallback );
|
|
ConVar mp_respawnwavetime_t( "mp_respawnwavetime_t", "10.0", FCVAR_REPLICATED | FCVAR_RELEASE, "Time between respawn waves for Terrorists.", RespawnWaveTimeTCallback );
|
|
|
|
#ifndef CLIENT_DLL
|
|
// Announcing is always on in REL build
|
|
|
|
// training
|
|
ConVar tr_completed_training(
|
|
"tr_completed_training",
|
|
"0",
|
|
FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE | FCVAR_SS,
|
|
"Whether the local player has completed the initial training portion of the training map" );
|
|
|
|
ConVar tr_best_course_time(
|
|
"tr_best_course_time",
|
|
"0",
|
|
FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE | FCVAR_SS,
|
|
"The player's best time for the timed obstacle course" );
|
|
|
|
ConVar tr_valve_course_time(
|
|
"tr_valve_course_time",
|
|
"352",
|
|
FCVAR_DEVELOPMENTONLY,
|
|
"Valve's best time for the timed obstacle course" );
|
|
|
|
ConVar mp_competitive_endofmatch_extra_time(
|
|
"mp_competitive_endofmatch_extra_time",
|
|
"15",
|
|
FCVAR_RELEASE,
|
|
"After a competitive match finishes rematch voting extra time is given for rankings." );
|
|
#endif
|
|
|
|
ConVar mp_endmatch_votenextmap(
|
|
"mp_endmatch_votenextmap",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Whether or not players vote for the next map at the end of the match when the final scoreboard comes up" );
|
|
|
|
ConVar mp_endmatch_votenextmap_keepcurrent(
|
|
"mp_endmatch_votenextmap_keepcurrent",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set, keeps the current map in the list of voting options. If not set, the current map will not appear in the list of voting options." );
|
|
|
|
ConVar mp_endmatch_votenextleveltime(
|
|
"mp_endmatch_votenextleveltime",
|
|
"20",
|
|
FCVAR_RELEASE,
|
|
"If mp_endmatch_votenextmap is set, players have this much time to vote on the next map at match end." );
|
|
|
|
// music controls
|
|
|
|
ConVar snd_music_boost(
|
|
"snd_music_boost",
|
|
"0",
|
|
FCVAR_REPLICATED,
|
|
"Specifies an amount to boost music volume by" );
|
|
|
|
#ifdef CLIENT_DLL
|
|
ConVar snd_music_selection(
|
|
"snd_music_selection",
|
|
"1",
|
|
FCVAR_ARCHIVE,
|
|
"Tracking rotating music for players with no music packs equipped.");
|
|
|
|
extern ConVar cl_borrow_music_from_player_index;
|
|
#endif
|
|
|
|
ConVar sv_endmatch_item_drop_interval(
|
|
"sv_endmatch_item_drop_interval",
|
|
"1.0",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"The time between drops on the end match scoreboard " );
|
|
|
|
ConVar sv_endmatch_item_drop_interval_rare(
|
|
"sv_endmatch_item_drop_interval_rare",
|
|
"1.0",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"The time between drops on the end match scoreboard for rare items " );
|
|
|
|
ConVar sv_endmatch_item_drop_interval_mythical(
|
|
"sv_endmatch_item_drop_interval_mythical",
|
|
"1.25",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"The time between drops on the end match scoreboard for mythical items " );
|
|
|
|
ConVar sv_endmatch_item_drop_interval_legendary(
|
|
"sv_endmatch_item_drop_interval_legendary",
|
|
"2.0",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"The time between drops on the end match scoreboard for legendary items " );
|
|
|
|
ConVar sv_endmatch_item_drop_interval_ancient(
|
|
"sv_endmatch_item_drop_interval_ancient",
|
|
"3.5",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"The time between drops on the end match scoreboard for ancient items " );
|
|
|
|
// bot difficulty tracking per user input device
|
|
ConVar sv_compute_per_bot_difficulty(
|
|
"sv_compute_per_bot_difficulty",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"0 = compute all bot difficulties equally, 1 = compute unique bot difficulty for each bot " );
|
|
|
|
ConVar sv_show_bot_difficulty_in_name(
|
|
"sv_show_bot_difficulty_in_name",
|
|
"0",
|
|
FCVAR_REPLICATED,
|
|
"0 = hide bot difficulty in bot name, 1 = show bot difficulty in bot name" );
|
|
|
|
ConVar sv_bot_difficulty_kbm(
|
|
"sv_bot_difficulty_kbm",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with Keyboard/Mouse device" );
|
|
|
|
ConVar sv_bot_difficulty_gamepad(
|
|
"sv_bot_difficulty_gamepad",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with Gamepad device" );
|
|
|
|
ConVar sv_bot_difficulty_ps3move(
|
|
"sv_bot_difficulty_ps3move",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with PS3Move device" );
|
|
|
|
ConVar sv_bot_difficulty_hydra(
|
|
"sv_bot_difficulty_hydra",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with Hydra device" );
|
|
|
|
ConVar sv_bot_difficulty_sharpshooter(
|
|
"sv_bot_difficulty_sharpshooter",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with SharpShooter device" );
|
|
|
|
ConVar sv_competitive_official_5v5( "sv_competitive_official_5v5",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"Enable to force the server to show 5v5 scoreboards and allows spectators to see characters through walls." );
|
|
|
|
ConVar sv_kick_ban_duration( "sv_kick_ban_duration",
|
|
"15",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"How long should a kick ban from the server should last (in minutes)" );
|
|
|
|
ConVar sv_disable_immunity_alpha( "sv_disable_immunity_alpha",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"If set, clients won't slam the player model render settings each frame for immunity [mod authors use this]" );
|
|
|
|
#ifdef CLIENT_DLL
|
|
ConVar cl_bot_difficulty_kbm(
|
|
"cl_bot_difficulty_kbm",
|
|
"0",
|
|
FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with Keyboard/Mouse device" );
|
|
|
|
ConVar cl_bot_difficulty_gamepad(
|
|
"cl_bot_difficulty_gamepad",
|
|
"0",
|
|
FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with Gamepad device" );
|
|
|
|
ConVar cl_bot_difficulty_ps3move(
|
|
"cl_bot_difficulty_ps3move",
|
|
"0",
|
|
FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with PS3Move device" );
|
|
|
|
ConVar cl_bot_difficulty_hydra(
|
|
"cl_bot_difficulty_hydra",
|
|
"0",
|
|
FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with Hydra device" );
|
|
|
|
ConVar cl_bot_difficulty_sharpshooter(
|
|
"cl_bot_difficulty_sharpshooter",
|
|
"0",
|
|
FCVAR_HIDDEN,
|
|
"Bot difficulty while playing with SharpShooter device" );
|
|
#endif
|
|
|
|
// Set game rules to allow all clients to talk to each other.
|
|
// Muted players still can't talk to each other.
|
|
ConVar sv_alltalk( "sv_alltalk", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE, "Deprecated. Replaced with sv_talk_enemy_dead and sv_talk_enemy_living." );
|
|
|
|
// [jason] Can the dead speak to the living?
|
|
ConVar sv_deadtalk( "sv_deadtalk", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE, "Dead players can speak (voice, text) to the living" );
|
|
|
|
// [jason] Override that removes all chat restrictions, including those for spectators
|
|
ConVar sv_full_alltalk( "sv_full_alltalk", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Any player (including Spectator team) can speak to any other player" );
|
|
|
|
ConVar sv_talk_enemy_dead( "sv_talk_enemy_dead", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Dead players can hear all dead enemy communication (voice, chat)" );
|
|
ConVar sv_talk_enemy_living( "sv_talk_enemy_living", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Living players can hear all living enemy communication (voice, chat)" );
|
|
|
|
#ifdef GAME_DLL
|
|
ConVar sv_auto_full_alltalk_during_warmup_half_end( "sv_auto_full_alltalk_during_warmup_half_end", "1", FCVAR_RELEASE, "When enabled will automatically turn on full all talk mode in warmup, at halftime and at the end of the match" );
|
|
#endif
|
|
|
|
ConVar sv_spec_hear( "sv_spec_hear", "1", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE, "Determines who spectators can hear: 0: only spectators; 1: all players; 2: spectated team; 3: self only; 4: nobody" );
|
|
|
|
ConVar mp_c4timer(
|
|
"mp_c4timer",
|
|
"40",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"how long from when the C4 is armed until it blows",
|
|
true, 10, // min value
|
|
false, 0 // max value
|
|
);
|
|
|
|
namespace SpecHear
|
|
{
|
|
enum Type
|
|
{
|
|
OnlySpectators = 0,
|
|
AllPlayers = 1,
|
|
SpectatedTeam = 2,
|
|
Self = 3,
|
|
Nobody = 4,
|
|
};
|
|
}
|
|
|
|
// NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc.
|
|
char *sTeamNames[] =
|
|
{
|
|
"Unassigned",
|
|
"Spectator",
|
|
"TERRORIST",
|
|
"CT"
|
|
};
|
|
|
|
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
ConVar cl_autowepswitch(
|
|
"cl_autowepswitch",
|
|
"1",
|
|
FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE| FCVAR_SS | FCVAR_USERINFO,
|
|
"Automatically switch to picked up weapons (if more powerful)" );
|
|
|
|
ConVar cl_use_opens_buy_menu(
|
|
"cl_use_opens_buy_menu",
|
|
"1",
|
|
FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE| FCVAR_SS | FCVAR_USERINFO,
|
|
"Pressing the +use key will open the buy menu if in a buy zone (just as if you pressed the 'buy' key)." );
|
|
|
|
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 1000 // 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",
|
|
"env_tonemap_controller",
|
|
"env_cascade_light",
|
|
"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_enemy_terrorist_spawn",
|
|
"info_deathmatch_spawn",
|
|
"info_armsrace_counterterrorist",
|
|
"info_armsrace_terrorist",
|
|
"info_map_parameters",
|
|
"keyframe_rope",
|
|
"move_rope",
|
|
"info_ladder",
|
|
"player",
|
|
"point_viewcontrol",
|
|
"point_viewcontrol_multiplayer",
|
|
"scene_manager",
|
|
"shadow_control",
|
|
"sky_camera",
|
|
"soundent",
|
|
"trigger_soundscape",
|
|
"viewmodel",
|
|
"predicted_viewmodel",
|
|
"worldspawn",
|
|
"point_devshot_camera",
|
|
"logic_choreographed_scene",
|
|
"cfe_player_decal", // persistent player spray decals must be preserved
|
|
//"logic_auto", // preserving this will break all of the maps who currently rely on it getting destroyed each time the map entities are recreated
|
|
"info_bomb_target_hint_A",
|
|
"info_bomb_target_hint_B",
|
|
"info_hostage_rescue_zone_hint",
|
|
// for the training map
|
|
"generic_actor",
|
|
"vote_controller",
|
|
"wearable_item",
|
|
"point_hiding_spot",
|
|
"game_coopmission_manager",
|
|
"chicken",
|
|
"", // END Marker
|
|
};
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// Voice helper
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
class CVoiceGameMgrHelper : public IVoiceGameMgrHelper
|
|
{
|
|
public:
|
|
virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity )
|
|
{
|
|
if ( pListener == NULL || pTalker == NULL )
|
|
return false;
|
|
|
|
if ( !CSGameRules() )
|
|
return false;
|
|
|
|
return CSGameRules()->CanPlayerHearTalker( pListener, pTalker, false );
|
|
}
|
|
};
|
|
CVoiceGameMgrHelper g_VoiceGameMgrHelper;
|
|
IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper;
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// Globals.
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
ConVar dev_reportmoneychanges(
|
|
"dev_reportmoneychanges",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
|
|
"Displays money account changes for players in the console" );
|
|
|
|
ConVar mp_roundtime(
|
|
"mp_roundtime",
|
|
"5",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"How many minutes each round takes.",
|
|
true, 1, // min value
|
|
true, 60 // max value
|
|
);
|
|
|
|
ConVar mp_roundtime_deployment(
|
|
"mp_roundtime_deployment",
|
|
"5",
|
|
FCVAR_RELEASE,
|
|
"How many minutes deployment for coop mission takes.",
|
|
true, 1, // min value
|
|
true, 15 // max value
|
|
);
|
|
|
|
ConVar mp_roundtime_hostage(
|
|
"mp_roundtime_hostage",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"How many minutes each round of Hostage Rescue takes. If 0 then use mp_roundtime instead.",
|
|
true, 0, // min value
|
|
true, 60 // max value
|
|
);
|
|
|
|
ConVar mp_roundtime_defuse(
|
|
"mp_roundtime_defuse",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"How many minutes each round of Bomb Defuse takes. If 0 then use mp_roundtime instead.",
|
|
true, 0, // min value
|
|
true, 60 // max value
|
|
);
|
|
|
|
ConVar mp_freezetime(
|
|
"mp_freezetime",
|
|
"6",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"how many seconds to keep players frozen when the round starts",
|
|
true, 0, // min value
|
|
true, 60 // max value
|
|
);
|
|
|
|
ConVar mp_limitteams(
|
|
"mp_limitteams",
|
|
"2",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"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 | FCVAR_RELEASE,
|
|
"Will TK'ers and team damagers be punished in the next round? {0=no, 1=yes}" );
|
|
|
|
ConVar mp_autokick(
|
|
"mp_autokick",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Kick idle/team-killing/team-damaging players" );
|
|
|
|
ConVar mp_spawnprotectiontime(
|
|
"mp_spawnprotectiontime",
|
|
"5",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Kick players who team-kill within this many seconds of a round restart." );
|
|
|
|
ConVar mp_td_spawndmgthreshold(
|
|
"mp_td_spawndmgthreshold",
|
|
"50",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The damage threshold players have to exceed at the start of the round to be warned/kick." );
|
|
|
|
ConVar mp_td_dmgtowarn(
|
|
"mp_td_dmgtowarn",
|
|
"200",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The damage threshhold players have to exceed in a match to get warned that they are about to be kicked." );
|
|
|
|
ConVar mp_td_dmgtokick(
|
|
"mp_td_dmgtokick",
|
|
"300",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The damage threshhold players have to exceed in a match to get kicked." );
|
|
|
|
ConVar mp_humanteam(
|
|
"mp_humanteam",
|
|
"any",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Restricts human players to a single team {any, CT, T}" );
|
|
|
|
ConVar mp_guardian_special_kills_needed(
|
|
"mp_guardian_special_kills_needed",
|
|
"10",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The number of kills needed with a specific weapon." );
|
|
|
|
ConVar mp_guardian_special_weapon_needed(
|
|
"mp_guardian_special_weapon_needed",
|
|
"awp",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The weapon that needs to be used to increment the kills needed to complete the mission." );
|
|
|
|
ConVar mp_guardian_player_dist_min(
|
|
"mp_guardian_player_dist_min",
|
|
"1300",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The distance at which we start to warn a player when they are too far from the guarded bombsite." );
|
|
|
|
ConVar mp_guardian_player_dist_max(
|
|
"mp_guardian_player_dist_max",
|
|
"2000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The maximum distance a player is allowed to get from the bombsite before they're killed." );
|
|
|
|
ConVar mp_guardian_bot_money_per_wave(
|
|
"mp_guardian_bot_money_per_wave",
|
|
"800",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"The amount of money bots get time each wave the players complete. This # is absolute and not additive, the money is set to (this)x(wave#) for each bot on each wave." );
|
|
|
|
ConVar mp_ignore_round_win_conditions(
|
|
"mp_ignore_round_win_conditions",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Ignore conditions which would end the current round" );
|
|
|
|
ConVar mp_dm_time_between_bonus_min(
|
|
"mp_dm_time_between_bonus_min",
|
|
"30",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Minimum time a bonus time will start after the round start or after the last bonus (in seconds)" );
|
|
|
|
ConVar mp_dm_time_between_bonus_max(
|
|
"mp_dm_time_between_bonus_max",
|
|
"40",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Maximum time a bonus time will start after the round start or after the last bonus (in seconds)" );
|
|
|
|
ConVar mp_dm_bonus_length_min(
|
|
"mp_dm_bonus_length_min",
|
|
"30",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Minimum time the bonus time will last (in seconds)" );
|
|
|
|
ConVar mp_dm_bonus_length_max(
|
|
"mp_dm_bonus_length_max",
|
|
"30",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"Maximum time the bonus time will last (in seconds)" );
|
|
|
|
ConVar mp_damage_scale_ct_body(
|
|
"mp_damage_scale_ct_body",
|
|
"1.0",
|
|
FCVAR_REPLICATED,
|
|
"Scales the damage a CT player takes by this much when they take damage in the body. (1 == 100%, 0.5 == 50%)" );
|
|
|
|
ConVar mp_damage_scale_ct_head(
|
|
"mp_damage_scale_ct_head",
|
|
"1.0",
|
|
FCVAR_REPLICATED,
|
|
"Scales the damage a CT player takes by this much when they take damage in the head (1 == 100%, 0.5 == 50%). REMEMBER! headshots do 4x the damage of the body before this scaler is applied." );
|
|
|
|
ConVar mp_damage_scale_t_body(
|
|
"mp_damage_scale_t_body",
|
|
"1.0",
|
|
FCVAR_REPLICATED,
|
|
"Scales the damage a T player takes by this much when they take damage in the body. (1 == 100%, 0.5 == 50%)" );
|
|
|
|
ConVar mp_damage_scale_t_head(
|
|
"mp_damage_scale_t_head",
|
|
"1.0",
|
|
FCVAR_REPLICATED,
|
|
"Scales the damage a T player takes by this much when they take damage in the head (1 == 100%, 0.5 == 50%). REMEMBER! headshots do 4x the damage of the body before this scaler is applied." );
|
|
|
|
ConVar mp_player_healthbuffer_decay_rate(
|
|
"mp_player_healthbuffer_decay_rate",
|
|
"0",
|
|
FCVAR_REPLICATED,
|
|
"When a player has buffer health, this is how fast it ticks down." );
|
|
|
|
ConCommand EndRound( "endround", &CCSGameRules::EndRound, "End the current round.", FCVAR_CHEAT );
|
|
|
|
void cc_ReportEntitiesInEntList( const CCommand& args )
|
|
{
|
|
//int nNumEnts = gEntList.NumberOfEntities();
|
|
for ( CBaseEntity *pClass = gEntList.FirstEnt(); pClass != NULL; pClass = gEntList.NextEnt( pClass ) )
|
|
{
|
|
if ( pClass /*&& !pClass->IsDormant()*/ )
|
|
{
|
|
Msg( "%s\n", pClass->GetClassname() );
|
|
}
|
|
}
|
|
}
|
|
|
|
static ConCommand ent_list_report( "ent_list_report", cc_ReportEntitiesInEntList, "Reports all list of all entities in a map, one by one" );
|
|
|
|
|
|
//number_of_entities = gEntList.NumberOfEntities();
|
|
|
|
CON_COMMAND_F ( tv_time_remaining, "Print remaining tv broadcast time", FCVAR_RELEASE | FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS )
|
|
{
|
|
#ifdef GAME_DLL
|
|
if ( HLTVDirector() && HLTVDirector()->IsActive() )
|
|
{
|
|
CEngineHltvInfo_t engineHltv;
|
|
if ( engine->GetEngineHltvInfo( engineHltv ) &&
|
|
engineHltv.m_bBroadcastActive && ( engineHltv.m_numClients > 0 ) )
|
|
{
|
|
if ( CSGameRules()->GetMatch()->GetPhase() != GAMEPHASE_MATCH_ENDED )
|
|
{
|
|
ConMsg( "GOTV spectators are attached. Match is still in progress.\n" );
|
|
}
|
|
else
|
|
{
|
|
float flTimeRemaining = ( CSGameRules()->GetIntermissionStartTime() + HLTVDirector()->GetDelay() + 5.0 ) - gpGlobals->curtime;
|
|
|
|
if ( flTimeRemaining > 0 )
|
|
{
|
|
ConMsg("GOTV spectators are attached. %f seconds remaining to broadcast.\n", ( CSGameRules()->GetIntermissionStartTime() + HLTVDirector()->GetDelay() + 5.0 ) - gpGlobals->curtime );
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "GOTV spectators are attached. GOTV Broadcast is complete.\n" );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ConMsg( "There are no GOTV spectators attached.\n" );
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
ConMsg( "GOTV is not active.\n" );
|
|
}
|
|
|
|
CON_COMMAND_F ( reset_expo, "Reset player scores, player controls, team scores, and end the round", FCVAR_CHEAT | FCVAR_GAMEDLL )
|
|
{
|
|
CSGameRules()->ResetForTradeshow();
|
|
}
|
|
|
|
CON_COMMAND_F ( tweak_ammo_impulses, "Allow real-time tweaking of the ammo impulse values.", FCVAR_CHEAT | FCVAR_GAMEDLL)
|
|
{
|
|
for (int ii=0; ii<MAX_AMMO_TYPES; ++ii )
|
|
{
|
|
GetCSAmmoDef()->m_AmmoType[ii].pPhysicsForceImpulse = USE_CVAR;
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// Contribution score control values
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
ConVar score_default(
|
|
"score_default",
|
|
"1000",
|
|
FCVAR_NONE,
|
|
"Default points for a new user" );
|
|
|
|
ConVar score_kill_enemy_bonus(
|
|
"score_kill_enemy_bonus",
|
|
"0",
|
|
FCVAR_NONE,
|
|
"Points awarded for killing an enemy" );
|
|
|
|
ConVar score_damage(
|
|
"score_damage",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Points awarded for each point of damage to an enemy" );
|
|
|
|
ConVar score_ff_damage(
|
|
"score_ff_damage",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Penalty awarded for each point of damage to a teammate" );
|
|
|
|
ConVar score_team_damage_bonus(
|
|
"score_team_damage_bonus",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Points awarded for each point of damage a nearby (in same zone) teammate does to enemies" );
|
|
|
|
ConVar score_planted_bomb_proximity_damage_bonus(
|
|
"score_planted_bomb_proximity_damage_bonus",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Points awarded for damaging enemy near planted bomb" );
|
|
|
|
ConVar score_planted_bomb_proximity_damage_radius_inner(
|
|
"score_planted_bomb_proximity_damage_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for doing damage near planted bomb" );
|
|
|
|
ConVar score_planted_bomb_proximity_damage_radius_outer(
|
|
"score_planted_bomb_proximity_damage_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for doing damage near planted bomb" );
|
|
|
|
ConVar score_hostage_proximity_damage_bonus(
|
|
"score_hostage_proximity_damage_bonus",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Points awarded for damaging enemy near live hostage" );
|
|
|
|
ConVar score_hostage_proximity_damage_radius_inner(
|
|
"score_hostage_proximity_damage_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for doing damage near hostage" );
|
|
|
|
ConVar score_hostage_proximity_damage_radius_outer(
|
|
"score_hostage_proximity_damage_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for doing damage near hostage" );
|
|
|
|
ConVar score_dropped_bomb_proximity_damage_bonus(
|
|
"score_dropped_bomb_proximity_damage_bonus",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Points awarded for damaging enemy near dropped bomb" );
|
|
|
|
ConVar score_dropped_bomb_proximity_damage_bonus_radius_inner(
|
|
"score_dropped_bomb_proximity_damage_bonus_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for doing damage near dropped bomb" );
|
|
|
|
ConVar score_dropped_bomb_proximity_damage_bonus_radius_outer(
|
|
"score_dropped_bomb_proximity_damage_bonus_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for doing damage near dropped bomb" );
|
|
|
|
ConVar score_dropped_defuser_proximity_damage_bonus(
|
|
"score_dropped_defuser_proximity_damage_bonus",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"Points awarded for damaging enemy near dropped defuser" );
|
|
|
|
ConVar score_dropped_defuser_proximity_damage_radius_inner(
|
|
"score_dropped_defuser_proximity_damage_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for doing damage near dropped defuser" );
|
|
|
|
ConVar score_dropped_defuser_proximity_damage_radius_outer(
|
|
"score_dropped_defuser_proximity_damage_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for doing damage near dropped defuser" );
|
|
|
|
ConVar score_bomb_plant_bonus(
|
|
"score_bomb_plant_bonus",
|
|
"200",
|
|
FCVAR_NONE,
|
|
"Points awarded for planting or assisting with planting the bomb" );
|
|
|
|
ConVar score_bomb_plant_radius_inner(
|
|
"score_bomb_plant_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for planting or assisting with planting the bomb" );
|
|
|
|
ConVar score_bomb_plant_radius_outer(
|
|
"score_bomb_plant_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for planting or assisting with planting the bomb" );
|
|
|
|
ConVar score_bomb_defuse_bonus(
|
|
"score_bomb_defuse_bonus",
|
|
"400",
|
|
FCVAR_NONE,
|
|
"Points awarded for defusing or assisting with defuse of bomb" );
|
|
|
|
ConVar score_bomb_defuse_radius_inner(
|
|
"score_bomb_defuse_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for defusing or assisting with defusing the bomb" );
|
|
|
|
ConVar score_bomb_defuse_radius_outer(
|
|
"score_bomb_defuse_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for defusing or assisting with defseing the bomb" );
|
|
|
|
ConVar score_hostage_rescue_bonus(
|
|
"score_hostage_rescue_bonus",
|
|
"100",
|
|
FCVAR_NONE,
|
|
"Points awarded for rescuing a hostage" );
|
|
|
|
ConVar score_hostage_rescue_radius_inner(
|
|
"score_hostage_rescue_radius_inner",
|
|
"120",
|
|
FCVAR_NONE,
|
|
"Inner radius (full bonus) for rescuing hostage" );
|
|
|
|
ConVar score_hostage_rescue_radius_outer(
|
|
"score_hostage_rescue_radius_outer",
|
|
"600",
|
|
FCVAR_NONE,
|
|
"Outer radius (zero bonus) for rescuing hostage" );
|
|
|
|
ConVar score_hostage_damage_penalty(
|
|
"score_hostage_damage_penalty",
|
|
"2",
|
|
FCVAR_NONE,
|
|
"Penalty for damaging a hostage" );
|
|
|
|
ConVar score_blind_enemy_bonus(
|
|
"score_blind_enemy_bonus",
|
|
"10",
|
|
FCVAR_NONE,
|
|
"Bonus for blinding enemy players" );
|
|
|
|
ConVar score_blind_friendly_penalty(
|
|
"score_blind_friendly_penalty",
|
|
"10",
|
|
FCVAR_NONE,
|
|
"Penalty for blinding friendly players" );
|
|
|
|
ConVar score_typical_good_score(
|
|
"score_typical_good_score",
|
|
"5",
|
|
FCVAR_NONE,
|
|
"An average good score for use in funfacts" );
|
|
|
|
ConVar contributionscore_assist(
|
|
"contributionscore_assist",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"amount of contribution score added for an assist" );
|
|
|
|
ConVar contributionscore_kill(
|
|
"contributionscore_kill",
|
|
"2",
|
|
FCVAR_NONE,
|
|
"amount of contribution score added for a kill" );
|
|
|
|
ConVar contributionscore_objective_kill(
|
|
"contributionscore_objective_kill",
|
|
"3",
|
|
FCVAR_NONE,
|
|
"amount of contribution score added for an objective related kill" );
|
|
|
|
ConVar contributionscore_hostage_rescue_minor(
|
|
"contributionscore_hostage_rescue_minor",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"amount of contribution score added to all alive CTs per hostage rescued" );
|
|
|
|
ConVar contributionscore_hostage_rescue_major(
|
|
"contributionscore_hostage_rescue_major",
|
|
"3",
|
|
FCVAR_NONE,
|
|
"amount of contribution score added to rescuer per hostage rescued" );
|
|
|
|
ConVar contributionscore_bomb_defuse_minor(
|
|
"contributionscore_bomb_defuse_minor",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"amount of contribution score for defusing a bomb after eliminating enemy team" );
|
|
|
|
ConVar contributionscore_bomb_defuse_major(
|
|
"contributionscore_bomb_defuse_major",
|
|
"3",
|
|
FCVAR_NONE,
|
|
"amount of contribution score for defusing a bomb while at least one enemy remains alive" );
|
|
|
|
|
|
ConVar contributionscore_bomb_planted(
|
|
"contributionscore_bomb_planted",
|
|
"2",
|
|
FCVAR_NONE,
|
|
"amount of contribution score for planting a bomb" );
|
|
|
|
ConVar contributionscore_bomb_exploded(
|
|
"contributionscore_bomb_exploded",
|
|
"1",
|
|
FCVAR_NONE,
|
|
"amount of contribution score awarded to bomb planter and terrorists remaining alive if bomb explosion wins the round" );
|
|
|
|
|
|
ConVar contributionscore_suicide(
|
|
"contributionscore_suicide",
|
|
"-2",
|
|
FCVAR_NONE,
|
|
"amount of contribution score for a suicide, normally negative" );
|
|
|
|
ConVar contributionscore_team_kill(
|
|
"contributionscore_team_kill",
|
|
"-2",
|
|
FCVAR_NONE,
|
|
"amount of contribution score for a team kill, normally negative" );
|
|
|
|
ConVar contributionscore_hostage_kill(
|
|
"contributionscore_hostage_kill",
|
|
"-2",
|
|
FCVAR_NONE,
|
|
"amount of contribution score for killing a hostage, normally negative" );
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// 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, unsigned int mask, ITraceFilter *pFilter )
|
|
{
|
|
// 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
|
|
//
|
|
|
|
CTraceFilterSimple defaultFilter( pMainEnt, COLLISION_GROUP_NONE );
|
|
if ( !pFilter )
|
|
{
|
|
pFilter = &defaultFilter;
|
|
}
|
|
|
|
Vector mins, maxs;
|
|
if ( pMainEnt )
|
|
{
|
|
pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
|
|
mins -= pMainEnt->GetAbsOrigin();
|
|
maxs -= pMainEnt->GetAbsOrigin();
|
|
}
|
|
else
|
|
{
|
|
mins = VEC_HULL_MIN;
|
|
maxs = VEC_HULL_MAX;
|
|
}
|
|
|
|
// 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, mask, pFilter ) )
|
|
{
|
|
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, mask, pFilter ) )
|
|
{
|
|
// 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, pFilter, &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;
|
|
}
|
|
|
|
// Returns the number of human spectators in the game
|
|
int UTIL_SpectatorsInGame( void )
|
|
{
|
|
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 ( entity->IsBot() )
|
|
continue;
|
|
|
|
if ( entity->GetTeamNumber() == TEAM_SPECTATOR )
|
|
{
|
|
iCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
int UTIL_HumansInGame( bool ignoreSpectators, bool ignoreUnassigned )
|
|
{
|
|
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;
|
|
|
|
if ( ignoreUnassigned && entity->GetTeamNumber() == TEAM_UNASSIGNED )
|
|
continue;
|
|
|
|
iCount++;
|
|
}
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
#if defined ( GAME_DLL )
|
|
|
|
bool CCSGameRules::CheckGotGuardianModeSpecialKill( CWeaponCSBase* pAttackerWeapon )
|
|
{
|
|
if ( IsPlayingCoopGuardian() == false )
|
|
return false;
|
|
|
|
if ( IsWarmupPeriod() )
|
|
return false;
|
|
|
|
if ( m_nGuardianModeSpecialWeaponNeeded != 0 )
|
|
{
|
|
if ( !pAttackerWeapon )
|
|
return false;
|
|
|
|
const CEconItemView *pEconItemViewWeapon = pAttackerWeapon->GetEconItemView();
|
|
if ( !pEconItemViewWeapon || !pEconItemViewWeapon->GetItemDefinition() )
|
|
return false;
|
|
|
|
if ( m_nGuardianModeSpecialWeaponNeeded != pEconItemViewWeapon->GetItemDefinition()->GetDefinitionIndex() )
|
|
return false;
|
|
}
|
|
|
|
// reduce # of kills needed
|
|
m_nGuardianModeSpecialKillsRemaining = MAX( m_nGuardianModeSpecialKillsRemaining - 1, 0 );
|
|
|
|
// REI: Should we send a game event message here? Right now we rely on network synchronization of
|
|
// m_nGuardianModeSpecialKillsRemaining and notice when it changes on the client to update
|
|
// their UI.
|
|
|
|
if ( m_nGuardianModeSpecialKillsRemaining <= 0 )
|
|
GuardianAllKillsAchievedCheck();
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if CS_CONTROLLABLE_BOTS_ENABLED
|
|
// DK TODO: Make a similar method run AFTER all loops of this to look for orphaned bots that think they are still player controlled
|
|
class RevertBotsFunctor
|
|
{
|
|
public:
|
|
bool operator()( CBasePlayer *basePlayer )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( basePlayer );
|
|
if ( !pPlayer )
|
|
return true;
|
|
|
|
if ( !pPlayer->IsControllingBot() )
|
|
return true;
|
|
|
|
// this will properly handle restoring money, frag counts, etc
|
|
pPlayer->ReleaseControlOfBot();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
|
|
CCSMatch::CCSMatch()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
void CCSMatch::Reset( void )
|
|
{
|
|
m_actualRoundsPlayed = 0;
|
|
CSGameRules()->SetTotalRoundsPlayed( 0 );
|
|
m_nOvertimePlaying = 0;
|
|
CSGameRules()->SetOvertimePlaying( 0 );
|
|
|
|
m_ctScoreFirstHalf = 0;
|
|
m_ctScoreSecondHalf = 0;
|
|
m_ctScoreOvertime = 0;
|
|
m_ctScoreTotal = 0;
|
|
|
|
m_terroristScoreFirstHalf = 0;
|
|
m_terroristScoreSecondHalf = 0;
|
|
m_terroristScoreOvertime = 0;
|
|
m_terroristScoreTotal = 0;
|
|
|
|
if ( CSGameRules()->HasHalfTime() )
|
|
{
|
|
SetPhase( GAMEPHASE_PLAYING_FIRST_HALF );
|
|
}
|
|
else
|
|
{
|
|
SetPhase( GAMEPHASE_PLAYING_STANDARD );
|
|
}
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
void CCSMatch::SetPhase( GamePhase phase )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( ( m_phase == GAMEPHASE_HALFTIME ) && mp_halftime_pausematch.GetInt() && pRules )
|
|
{ // when halftime is over, we pause the match if needed
|
|
if ( !pRules->IsMatchWaitingForResume() )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Match_Will_Pause" );
|
|
}
|
|
pRules->SetMatchWaitingForResume( true );
|
|
}
|
|
|
|
m_phase = phase;
|
|
|
|
// When going to overtime halftime pause the timer if requested
|
|
if ( ( m_phase == GAMEPHASE_HALFTIME ) && m_nOvertimePlaying && mp_overtime_halftime_pausetimer.GetInt() )
|
|
mp_halftime_pausetimer.SetValue( mp_overtime_halftime_pausetimer.GetInt() );
|
|
|
|
EnableFullAlltalk( CSGameRules()->IsWarmupPeriod() || m_phase == GAMEPHASE_HALFTIME || m_phase == GAMEPHASE_MATCH_ENDED );
|
|
|
|
CSGameRules()->SetGamePhase( phase );
|
|
}
|
|
|
|
void CCSMatch::AddTerroristWins( int numWins )
|
|
{
|
|
m_actualRoundsPlayed += numWins;
|
|
CSGameRules()->SetTotalRoundsPlayed( m_actualRoundsPlayed );
|
|
AddTerroristScore( numWins );
|
|
}
|
|
|
|
void CCSMatch::AddCTWins( int numWins )
|
|
{
|
|
m_actualRoundsPlayed += numWins;
|
|
CSGameRules()->SetTotalRoundsPlayed( m_actualRoundsPlayed );
|
|
AddCTScore( numWins );
|
|
}
|
|
|
|
void CCSMatch::IncrementRound( int nNumRounds )
|
|
{
|
|
m_actualRoundsPlayed += nNumRounds;
|
|
CSGameRules()->SetTotalRoundsPlayed( m_actualRoundsPlayed );
|
|
}
|
|
|
|
void CCSMatch::AddTerroristBonusPoints( int points )
|
|
{
|
|
AddTerroristScore( points );
|
|
}
|
|
|
|
void CCSMatch::AddCTBonusPoints( int points)
|
|
{
|
|
AddCTScore( points );
|
|
}
|
|
|
|
void CCSMatch::AddTerroristScore( int score )
|
|
{
|
|
m_terroristScoreTotal += score;
|
|
|
|
if ( m_nOvertimePlaying > 0 )
|
|
{
|
|
m_terroristScoreOvertime += score;
|
|
}
|
|
else if ( m_phase == GAMEPHASE_PLAYING_FIRST_HALF )
|
|
{
|
|
m_terroristScoreFirstHalf += score;
|
|
}
|
|
else if ( m_phase == GAMEPHASE_PLAYING_SECOND_HALF )
|
|
{
|
|
m_terroristScoreSecondHalf += score;
|
|
}
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
void CCSMatch::AddCTScore( int score )
|
|
{
|
|
m_ctScoreTotal += score;
|
|
|
|
if ( m_nOvertimePlaying > 0 )
|
|
{
|
|
m_ctScoreOvertime += score;
|
|
}
|
|
else if ( m_phase == GAMEPHASE_PLAYING_FIRST_HALF )
|
|
{
|
|
m_ctScoreFirstHalf += score;
|
|
}
|
|
else if ( m_phase == GAMEPHASE_PLAYING_SECOND_HALF )
|
|
{
|
|
m_ctScoreSecondHalf += score;
|
|
}
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
void CCSMatch::GoToOvertime( int numOvertimesToAdd )
|
|
{
|
|
m_nOvertimePlaying += numOvertimesToAdd;
|
|
CSGameRules()->SetOvertimePlaying( m_nOvertimePlaying );
|
|
}
|
|
|
|
void CCSMatch::SwapTeamScores( void )
|
|
{
|
|
short temp = m_terroristScoreFirstHalf;
|
|
m_terroristScoreFirstHalf = m_ctScoreFirstHalf;
|
|
m_ctScoreFirstHalf = temp;
|
|
|
|
temp = m_terroristScoreSecondHalf;
|
|
m_terroristScoreSecondHalf = m_ctScoreSecondHalf;
|
|
m_ctScoreSecondHalf = temp;
|
|
|
|
temp = m_terroristScoreOvertime;
|
|
m_terroristScoreOvertime = m_ctScoreOvertime;
|
|
m_ctScoreOvertime = temp;
|
|
|
|
temp = m_terroristScoreTotal;
|
|
m_terroristScoreTotal = m_ctScoreTotal;
|
|
m_ctScoreTotal = temp;
|
|
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
void CCSMatch::UpdateTeamScores( void )
|
|
{
|
|
CTeam *pTerrorists = GetGlobalTeam( TEAM_TERRORIST );
|
|
CTeam *pCTs = GetGlobalTeam( TEAM_CT );
|
|
|
|
if ( pTerrorists )
|
|
{
|
|
pTerrorists->SetScore( m_terroristScoreTotal );
|
|
pTerrorists->SetScoreFirstHalf( m_terroristScoreFirstHalf );
|
|
pTerrorists->SetScoreSecondHalf( m_terroristScoreSecondHalf );
|
|
pTerrorists->SetScoreOvertime( m_terroristScoreOvertime );
|
|
}
|
|
|
|
if ( pCTs )
|
|
{
|
|
pCTs->SetScore( m_ctScoreTotal);
|
|
pCTs->SetScoreFirstHalf( m_ctScoreFirstHalf );
|
|
pCTs->SetScoreSecondHalf( m_ctScoreSecondHalf );
|
|
pCTs->SetScoreOvertime( m_ctScoreOvertime );
|
|
}
|
|
}
|
|
|
|
void CCSMatch::EnableFullAlltalk( bool bEnable )
|
|
{
|
|
if ( !sv_auto_full_alltalk_during_warmup_half_end.GetBool() )
|
|
bEnable = false;
|
|
|
|
static ConVarRef sv_full_alltalk( "sv_full_alltalk" );
|
|
sv_full_alltalk.SetValue( bEnable );
|
|
}
|
|
|
|
int CCSMatch::GetWinningTeam( void )
|
|
{
|
|
CTeam *pTerrorists = GetGlobalTeam( TEAM_TERRORIST );
|
|
CTeam *pCTs = GetGlobalTeam( TEAM_CT );
|
|
|
|
if ( pTerrorists && pTerrorists->m_bSurrendered )
|
|
{
|
|
return TEAM_CT;
|
|
}
|
|
else if ( pCTs && pCTs->m_bSurrendered )
|
|
{
|
|
return TEAM_TERRORIST;
|
|
}
|
|
else if ( m_terroristScoreTotal > m_ctScoreTotal )
|
|
{
|
|
return TEAM_TERRORIST;
|
|
}
|
|
else if ( m_terroristScoreTotal < m_ctScoreTotal )
|
|
{
|
|
return TEAM_CT;
|
|
}
|
|
else
|
|
{
|
|
return WINNER_NONE;
|
|
}
|
|
}
|
|
|
|
template < class T > void VectorShuffle( CUtlVector< T > &arrayToShuffle )
|
|
{
|
|
int numEntries = arrayToShuffle.Count();
|
|
|
|
// Shuffle entries
|
|
for ( int i = 0; i < numEntries - 1; ++i )
|
|
{
|
|
int randVal = RandomInt( i, numEntries - 1 );
|
|
|
|
if ( randVal != i )
|
|
{
|
|
// Swap values
|
|
V_swap( arrayToShuffle[ i ], arrayToShuffle[ randVal ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// CCSGameRules implementation.
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
CCSGameRules::GcBanInformationMap_t CCSGameRules::sm_mapGcBanInformation;
|
|
|
|
CCSGameRules::CCSGameRules()
|
|
{
|
|
m_flLastThinkTime = gpGlobals->curtime;
|
|
|
|
m_iRoundTime = 0;
|
|
m_gamePhase = GAMEPHASE_PLAYING_STANDARD;
|
|
m_iRoundWinStatus = WINNER_NONE;
|
|
m_eRoundWinReason = RoundEndReason_StillInProgress;
|
|
m_iFreezeTime = 0;
|
|
m_totalRoundsPlayed = 0;
|
|
m_nOvertimePlaying = 0;
|
|
|
|
m_fMatchStartTime = gpGlobals->curtime;
|
|
m_fRoundStartTime = 0;
|
|
m_bAllowWeaponSwitch = true;
|
|
m_bFreezePeriod = true;
|
|
m_bMatchWaitingForResume = false;
|
|
|
|
|
|
m_nTerroristTimeOuts = mp_team_timeout_max.GetInt();
|
|
m_nCTTimeOuts = mp_team_timeout_max.GetInt();
|
|
|
|
m_flTerroristTimeOutRemaining = mp_team_timeout_time.GetInt();
|
|
m_flCTTimeOutRemaining = mp_team_timeout_time.GetInt();
|
|
|
|
m_bTerroristTimeOutActive = false;
|
|
m_bCTTimeOutActive = false;
|
|
|
|
m_iNumTerrorist = m_iNumCT = 0; // number of players per team
|
|
m_flRestartRoundTime = cInitialRestartRoundTime; // restart first round as soon as possible
|
|
m_timeUntilNextPhaseStarts = 0.0f;
|
|
m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0;
|
|
m_bFirstConnected = false;
|
|
m_bCompleteReset = true;
|
|
m_bPickNewTeamsOnReset = true;
|
|
m_bScrambleTeamsOnRestart = false;
|
|
m_bSwapTeamsOnRestart = false;
|
|
m_iAccountTerrorist = m_iAccountCT = 0;
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
m_bTargetBombed = false;
|
|
m_bBombDefused = false;
|
|
m_iTotalRoundsPlayed = -1;
|
|
m_endMatchOnRoundReset = false;
|
|
m_endMatchOnThink = false;
|
|
m_iUnBalancedRounds = 0;
|
|
m_flGameStartTime = 0;
|
|
m_iHostagesRemaining = 0;
|
|
m_bAnyHostageReached = false;
|
|
m_bLevelInitialized = false;
|
|
m_flCoopRespawnAndHealTime = -1;
|
|
|
|
m_bLogoMap = false;
|
|
m_tmNextPeriodicThink = 0;
|
|
m_bPlayerItemsHaveBeenDisplayed = false;
|
|
|
|
m_flDMBonusStartTime = 0;
|
|
m_flDMBonusTimeLength = 0;
|
|
m_unDMBonusWeaponLoadoutSlot = 0;
|
|
m_bDMBonusActive = false;
|
|
|
|
m_bIsDroppingItems = false;
|
|
m_iActiveAssassinationTargetMissionID = 0;
|
|
|
|
m_flGuardianBuyUntilTime = -1;
|
|
m_bCTCantBuy = false;
|
|
m_bTCantBuy = false;
|
|
m_bForceTeamChangeSilent = false;
|
|
m_bLoadingRoundBackupData = false;
|
|
m_pfnCalculateEndOfRoundMVPHook = NULL;
|
|
|
|
m_bMapHasBombTarget = false;
|
|
m_bMapHasRescueZone = false;
|
|
|
|
m_iSpawnPointCount_Terrorist = 0;
|
|
m_iSpawnPointCount_CT = 0;
|
|
|
|
m_bBuyTimeEnded = false;
|
|
|
|
m_bHasMatchStarted = false;
|
|
|
|
m_nLastFreezeEndBeep = -1;
|
|
|
|
m_iMaxNumTerrorists = 0;
|
|
m_iMaxNumCTs = 0;
|
|
|
|
m_bMapHasBuyZone = false;
|
|
|
|
m_nNextMapInMapgroup = -1;
|
|
|
|
m_bVoiceWonMatchBragFired = false;
|
|
|
|
m_iLoserBonus = 0;
|
|
|
|
m_iHostagesRescued = 0;
|
|
m_iHostagesTouched = 0;
|
|
m_flNextHostageAnnouncement = 0.0f;
|
|
|
|
m_MatchDevice = BotProfileInputDevice::KB_MOUSE;
|
|
|
|
// map vote for "season" map groups
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_MatchInProgress;
|
|
// clear the next level
|
|
nextlevel.SetValue( "" );
|
|
|
|
// Set the bestof maps state
|
|
m_numBestOfMaps = mp_teamscore_max.GetInt();
|
|
|
|
// Set global gifts state
|
|
m_numGlobalGiftsGiven = 0;
|
|
m_numGlobalGifters = 0;
|
|
m_numGlobalGiftsPeriodSeconds = 0;
|
|
for ( int j = 0; j < MAX_GIFT_GIVERS_FEATURED_COUNT; ++ j )
|
|
{
|
|
m_arrFeaturedGiftersAccounts.Set( j, 0 );
|
|
m_arrFeaturedGiftersGifts.Set( j, 0 );
|
|
}
|
|
CheckForGiftsLeaderboardUpdate();
|
|
|
|
for ( int j = 0; j < MAX_TOURNAMENT_ACTIVE_CASTER_COUNT; ++ j )
|
|
{
|
|
m_arrTournamentActiveCasterAccounts.Set( j, 0 );
|
|
}
|
|
|
|
// Configure QMM settings
|
|
m_bIsQuestEligible = IsQuestEligible();
|
|
m_bIsQueuedMatchmaking = IsQueuedMatchmaking();
|
|
m_bIsValveDS = IsValveDS();
|
|
m_pQueuedMatchmakingReservationString = NULL;
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_MatchInProgress;
|
|
m_bNeedToAskPlayersForContinueVote = false;
|
|
m_pQueuedMatchmakingReportedRoundStats = NULL;
|
|
m_numTotalTournamentDrops = 0;
|
|
m_numSpectatorsCountMax = 0;
|
|
m_numSpectatorsCountMaxTV = 0;
|
|
m_numSpectatorsCountMaxLnk = 0;
|
|
m_numQueuedMatchmakingAccounts = 0;
|
|
|
|
m_szTournamentEventName.GetForModify()[0] = 0;
|
|
m_szTournamentEventStage.GetForModify()[0] = 0;
|
|
Q_strncpy( m_szTournamentPredictionsTxt.GetForModify(), mp_teamprediction_txt.GetString(), MAX_PATH );
|
|
Q_strncpy( m_szMatchStatTxt.GetForModify(), mp_teammatchstat_txt.GetString(), MAX_PATH );
|
|
m_nTournamentPredictionsPct = 0;
|
|
m_nMatchInfoShowType = k_MapMatchInfoShownCounts_None;
|
|
m_flMatchInfoDecidedTime = gpGlobals->curtime;
|
|
|
|
m_mapQueuedMatchmakingPlayersData.PurgeAndDeleteElements();
|
|
|
|
for ( int j = 0; j < MAX_MATCH_STATS_ROUNDS; ++ j )
|
|
{
|
|
m_iMatchStats_PlayersAlive_T.GetForModify(j) = 0x3F;
|
|
m_iMatchStats_PlayersAlive_CT.GetForModify(j) = 0x3F;
|
|
}
|
|
|
|
// Halloween
|
|
m_nHalloweenMaskListSeed = RandomInt( 0, 30 );
|
|
#if 0
|
|
//
|
|
// Setting a bunch of overrides
|
|
//
|
|
sm_QueuedServerReservation.mutable_tournament_event()->set_event_name( "ESL One Katowice 2015 Vitaliy Test Championship" );
|
|
sm_QueuedServerReservation.mutable_tournament_event()->set_event_stage_name( "Group Stage | Decider Match" );
|
|
TournamentTeam *pTeam;
|
|
pTeam = sm_QueuedServerReservation.add_tournament_teams();
|
|
pTeam->set_team_tag( "NiP" );
|
|
pTeam->set_team_flag( "SE" );
|
|
pTeam->set_team_name( "Ninjas in Pyjamas" );
|
|
// pTeam->set_team_clantag( "NiP.Trig" );
|
|
if ( TournamentPlayer *pPlayer = pTeam->add_players() )
|
|
{
|
|
pPlayer->set_account_id( 102003 );
|
|
pPlayer->set_player_nick( "GeT_RiGhT" );
|
|
pPlayer->set_player_name( "Vitaliy Genkin" );
|
|
}
|
|
pTeam = sm_QueuedServerReservation.add_tournament_teams();
|
|
pTeam->set_team_tag( "NAVI" );
|
|
pTeam->set_team_flag( "UA" );
|
|
pTeam->set_team_name( "Natus Vincere" );
|
|
// pTeam->set_team_clantag( "NA'VI" );
|
|
if ( TournamentPlayer *pPlayer = pTeam->add_players() )
|
|
{
|
|
pPlayer->set_account_id( 102003 );
|
|
pPlayer->set_player_nick( "GeT_RiGhT" );
|
|
pPlayer->set_player_name( "Vitaliy Genkin" );
|
|
}
|
|
sm_QueuedServerReservation.mutable_pre_match_data()->set_predictions_pct( 72 );
|
|
CPreMatchInfoData_TeamStats *pTS;
|
|
#if 0 // Group A | Decider Match
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_37}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_26}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossVs{team=#CSGO_TeamID_24}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossVs{team=#CSGO_TeamID_31}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_Group2{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinAdvan" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossElim" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_Group2{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossElim" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinAdvan" );
|
|
#endif
|
|
#if 0 // Quarterfinal | Match 1 of 3
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_Series2{name=#CSGO_MatchInfo_Stage_Quarterfinal}{idx=1}{count=3}" );
|
|
pTS->add_match_info_teams()->assign( "0" );
|
|
pTS->add_match_info_teams()->assign( "0" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_Groups}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_QualPos1{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_QualPos2{name=#CSGO_MatchInfo_Stage_GroupB}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_Groups}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_37}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_26}" );
|
|
#endif
|
|
#if 0 // Quarterfinal | Match 2 of 3
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_Series2{name=#CSGO_MatchInfo_Stage_Quarterfinal}{idx=2}{count=3}" );
|
|
pTS->add_match_info_teams()->assign( "1" );
|
|
pTS->add_match_info_teams()->assign( "0" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_JustPlayedMap{map=#SFUI_Map_de_cbble}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinScoreMap{map=#SFUI_Map_de_cbble}{high=16}{low=3}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_Loss{map=#SFUI_Map_de_cbble}{high=16}{low=3}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_Groups}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_QualPos1{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_QualPos2{name=#CSGO_MatchInfo_Stage_GroupB}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_Groups}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_37}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_26}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_Series2{name=#CSGO_MatchInfo_Stage_Quarterfinal}{idx=2}{count=3}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinAdvan" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossElim" );
|
|
#endif
|
|
#if 1 // Quarterfinal | Match 3 of 3
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_BracketDecider{name=#CSGO_MatchInfo_Stage_Quarterfinal}" );
|
|
pTS->add_match_info_teams()->assign( "1" );
|
|
pTS->add_match_info_teams()->assign( "1" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_JustPlayedMaps" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinScoreMap{map=#SFUI_Map_de_cbble}{high=16}{low=3}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinScoreMap{map=#SFUI_Map_de_mirage}{high=23}{low=21}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_Groups}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_QualPos1{name=#CSGO_MatchInfo_Stage_GroupA}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_QualPos2{name=#CSGO_MatchInfo_Stage_GroupB}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_PreviouslyIn{name=#CSGO_MatchInfo_Stage_Groups}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_37}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinVs{team=#CSGO_TeamID_26}" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_BracketDecider{name=#CSGO_MatchInfo_Stage_Quarterfinal}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinAdvan" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossElim" );
|
|
pTS = sm_QueuedServerReservation.mutable_pre_match_data()->add_stats();
|
|
pTS->set_match_info_txt( "#CSGO_MatchInfoTxt_BracketDecider{name=#CSGO_MatchInfo_Stage_Quarterfinal}" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_LossElim" );
|
|
pTS->add_match_info_teams()->assign( "#CSGO_MatchInfoTeam_WinAdvan" );
|
|
#endif
|
|
#endif
|
|
|
|
if ( m_bIsQueuedMatchmaking )
|
|
{
|
|
static ConVarRef sv_mmqueue_reservation( "sv_mmqueue_reservation" );
|
|
m_pQueuedMatchmakingReservationString = new char[ Q_strlen( sv_mmqueue_reservation.GetString() ) + 1 ];
|
|
Q_strcpy( m_pQueuedMatchmakingReservationString, sv_mmqueue_reservation.GetString() );
|
|
int iDraftIndex = 0;
|
|
for ( char const *pszPrev = sv_mmqueue_reservation.GetString(), *pszNext = pszPrev;
|
|
( pszNext = strchr( pszPrev, '[' ) ) != NULL; ( pszPrev = pszNext + 1 ), ( ++ iDraftIndex ) )
|
|
{
|
|
uint32 uiAccountId = 0;
|
|
sscanf( pszNext, "[%x]", &uiAccountId );
|
|
if ( uiAccountId )
|
|
{
|
|
++ m_numQueuedMatchmakingAccounts;
|
|
|
|
CQMMPlayerData_t *pqmmPlayerData = new CQMMPlayerData_t;
|
|
pqmmPlayerData->m_uiPlayerAccountId = uiAccountId;
|
|
pqmmPlayerData->m_iDraftIndex = iDraftIndex;
|
|
if ( CCSGameRules::CQMMPlayerData_t *pQMM = CSGameRules()->QueuedMatchmakingPlayersDataFind( uiAccountId ) )
|
|
delete pQMM;
|
|
m_mapQueuedMatchmakingPlayersData.InsertOrReplace( uiAccountId, pqmmPlayerData );
|
|
}
|
|
}
|
|
|
|
// Set next level to the current level for potential rematch
|
|
nextlevel.SetValue( STRING( gpGlobals->mapname ) );
|
|
|
|
// Set the tournament settings
|
|
if ( sm_QueuedServerReservation.has_tournament_event() &&
|
|
sm_QueuedServerReservation.tournament_event().has_event_name() )
|
|
Q_strncpy( m_szTournamentEventName.GetForModify(), sm_QueuedServerReservation.tournament_event().event_name().c_str(), MAX_PATH );
|
|
if ( sm_QueuedServerReservation.has_tournament_event() &&
|
|
sm_QueuedServerReservation.tournament_event().has_event_stage_name() )
|
|
Q_strncpy( m_szTournamentEventStage.GetForModify(), sm_QueuedServerReservation.tournament_event().event_stage_name().c_str(), MAX_PATH );
|
|
}
|
|
|
|
// [tj] reset flawless and lossless round related flags
|
|
m_bNoTerroristsKilled = true;
|
|
m_bNoCTsKilled = true;
|
|
m_bNoTerroristsDamaged = true;
|
|
m_bNoCTsDamaged = true;
|
|
m_bNoEnemiesKilled = true;
|
|
m_pFirstKill = NULL;
|
|
m_firstKillTime = 0;
|
|
|
|
// [menglish] Reset fun fact values
|
|
m_pFirstBlood = NULL;
|
|
m_firstBloodTime = 0;
|
|
|
|
m_pMVP = NULL;
|
|
|
|
m_bCanDonateWeapons = true;
|
|
|
|
// [dwenger] Reset rescue-related achievement values
|
|
m_arrRescuers.RemoveAll();
|
|
|
|
m_hostageWasInjured = false;
|
|
m_hostageWasKilled = false;
|
|
|
|
m_pFunFactManager = new CCSFunFactMgr();
|
|
m_pFunFactManager->Init();
|
|
|
|
m_iHaveEscaped = 0;
|
|
m_bMapHasEscapeZone = false;
|
|
m_iNumEscapers = 0;
|
|
m_iNumEscapeRounds = 0;
|
|
|
|
m_bMapHasBombZone = false;
|
|
m_bBombDropped = false;
|
|
m_bBombPlanted = false;
|
|
m_bHasHostageBeenTouched = false;
|
|
m_bDontIncrementCoopWave = false;
|
|
|
|
m_bGunGameRespawnWithBomb = false;
|
|
m_fGunGameBombRespawnTimer = 0.0f;
|
|
m_bRoundTimeWarningTriggered = false;
|
|
|
|
m_iNumGunGameProgressiveWeaponsCT = 0;
|
|
m_iNumGunGameProgressiveWeaponsT = 0;
|
|
m_bAllowWeaponSwitch = true;
|
|
|
|
m_iSpectatorSlotCount = 0;
|
|
|
|
m_nGuardianModeWaveNumber = 1;
|
|
m_nGuardianModeSpecialKillsRemaining = -1;
|
|
m_nGuardianModeSpecialWeaponNeeded = -1;
|
|
m_nGuardianGrenadesToGiveBots = 0;
|
|
m_nNumHeaviesToSpawn = 0;
|
|
|
|
m_flNextHostageAnnouncement = gpGlobals->curtime; // asap.
|
|
|
|
m_phaseChangeAnnouncementTime = 0.0f;
|
|
m_fNextUpdateTeamClanNamesTime = 0.0f;
|
|
|
|
// 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 );
|
|
}
|
|
|
|
m_bHasTriggeredRoundStartMusic = false;
|
|
m_bHasTriggeredCoopSpawnReset = false;
|
|
|
|
InitializeGameTypeAndMode();
|
|
|
|
#ifndef VALVE_DEDICATED_SERVER
|
|
if ( const char* szMapNameBase = V_GetFileName( STRING(gpGlobals->mapname) ) )
|
|
{
|
|
if ( (IsPlayingCustomGametype() )
|
|
&& filesystem->FileExists( UTIL_VarArgs( "maps/cfg/%s.cfg", szMapNameBase ) ) )
|
|
{
|
|
// Execute a map specific cfg file to define the rules
|
|
engine->ServerCommand( UTIL_VarArgs( "execwithwhitelist %s.cfg */maps\n", szMapNameBase ) );
|
|
engine->ServerExecute();
|
|
}
|
|
else if ( IsPlayingCoopMission() )
|
|
{
|
|
// Execute a map specific cfg file to define the rules
|
|
int nMissionNumber = 1;
|
|
for ( KeyValues *kvLaunchOptions = engine->GetLaunchOptions()->GetFirstSubKey(); kvLaunchOptions; kvLaunchOptions = kvLaunchOptions->GetNextKey() )
|
|
{
|
|
if ( char const *szValue = StringAfterPrefix( kvLaunchOptions->GetString(), "mission" ) )
|
|
{
|
|
nMissionNumber = V_atoi( szValue );
|
|
DevMsg( "Coop mission number = %d (parsed from %s)\n", nMissionNumber, kvLaunchOptions->GetString() );
|
|
break;
|
|
}
|
|
}
|
|
engine->ServerCommand( UTIL_VarArgs( "execwithwhitelist %s_%d.cfg */maps\n", szMapNameBase, nMissionNumber ) );
|
|
engine->ServerExecute();
|
|
}
|
|
else if ( IsPlayingCoopGuardian() )
|
|
{
|
|
char szCfgName[ MAX_PATH ];
|
|
if ( filesystem->FileExists( UTIL_VarArgs( "maps/cfg/guardian_%s.cfg", szMapNameBase ) ) )
|
|
V_snprintf( szCfgName, sizeof( szCfgName ), "guardian_%s", szMapNameBase );
|
|
else
|
|
V_snprintf( szCfgName, sizeof( szCfgName ), "guardian_defaultmap" );
|
|
|
|
// Execute a map specific cfg file to define the rules
|
|
engine->ServerCommand( UTIL_VarArgs( "execwithwhitelist %s.cfg */maps\n", szCfgName ) );
|
|
engine->ServerExecute();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ReadMultiplayCvars();
|
|
|
|
m_bVoteCalled = false;
|
|
m_bServerVoteOnReset = false;
|
|
m_flVoteCheckThrottle = 0;
|
|
|
|
m_bGameRestart = false;
|
|
|
|
m_bSwitchingTeamsAtRoundReset = false;
|
|
|
|
m_fAutobalanceDisplayTime = 0.0f;
|
|
m_AutobalanceStatus = AutobalanceStatus::NONE;
|
|
|
|
m_iNextCTSpawnPoint = 0;
|
|
m_iNextTerroristSpawnPoint = 0;
|
|
|
|
m_iMaxGunGameProgressiveWeaponIndex = 0;
|
|
// m_flDeferredCallDispatchTime = 0.0f;
|
|
|
|
m_bWarmupPeriod = mp_do_warmup_period.GetBool();
|
|
m_fWarmupNextChatNoticeTime = 0;
|
|
m_fWarmupPeriodStart = gpGlobals->curtime;
|
|
m_coopMissionManager = NULL;
|
|
|
|
for ( int i = 0; i < MAX_TEAMS; i++ )
|
|
{
|
|
m_flNextRespawnWave.Set( i, 0 );
|
|
m_TeamRespawnWaveTimes.Set( i, -1.0f );
|
|
}
|
|
|
|
for ( int iVoteOption = 0; iVoteOption < MAX_ENDMATCH_VOTE_PANELS; ++ iVoteOption )
|
|
m_nEndMatchMapGroupVoteOptions.Set( iVoteOption, -1 );
|
|
|
|
|
|
|
|
m_coopBonusCoinsFound = 0;
|
|
m_coopBonusPistolsOnly = true;
|
|
m_coopPlayersInDeploymentZone = false;
|
|
|
|
//m_pSun = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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();
|
|
|
|
delete m_pFunFactManager;
|
|
m_pFunFactManager = NULL;
|
|
|
|
delete m_pQueuedMatchmakingReportedRoundStats;
|
|
m_pQueuedMatchmakingReportedRoundStats = NULL;
|
|
|
|
delete m_pQueuedMatchmakingReservationString;
|
|
m_pQueuedMatchmakingReservationString = NULL;
|
|
|
|
m_mapQueuedMatchmakingPlayersData.PurgeAndDeleteElements();
|
|
|
|
ClearItemsDroppedDuringMatch();
|
|
}
|
|
|
|
void CCSGameRules::RefreshSkillData( bool /* forceUpdate */ )
|
|
{
|
|
// NOTE[pmf]: The base class loads skill configuration files which we
|
|
// do not want for CS:GO
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CCSGameRules::UpdateClientData( CBasePlayer *player )
|
|
{
|
|
}
|
|
|
|
int CCSGameRules::GetMaxHumanPlayers() const
|
|
{
|
|
int iGameType = g_pGameTypes->GetCurrentGameType();
|
|
int iGameMode = g_pGameTypes->GetCurrentGameMode();
|
|
return g_pGameTypes->GetMaxPlayersForTypeAndMode( iGameType, iGameMode );
|
|
}
|
|
|
|
void CCSGameRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pEntity ) );
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
#if 0
|
|
// If a client sends up their medal rankings (based on achievements completed so far)
|
|
// store them in CSPlayer for display on the scoreboard.
|
|
if ( 0 == Q_strcmp( pKeyValues->GetName(), "player_medal_ranking" ) )
|
|
{
|
|
pPlayer->UpdateRankFromKV( pKeyValues );
|
|
}
|
|
else
|
|
#endif
|
|
if ( ( 0 == Q_strcmp( pKeyValues->GetName(), "ClanTagChanged" ) ) &&
|
|
// When we have tournament system enabled then players cannot change their clan tags from client
|
|
CanClientCustomizeOwnIdentity() )
|
|
{
|
|
pPlayer->SetClanTag( pKeyValues->GetString( "tag", "" ) );
|
|
const char *szClanName = pKeyValues->GetString( "name", "" );
|
|
pPlayer->SetClanName( szClanName );
|
|
|
|
//if ( !V_strcmp( team->Get_Name(), CSGameRules()->GetDefaultTeamName(TEAM_TERRORIST) ) )
|
|
|
|
UpdateTeamClanNames( TEAM_TERRORIST );
|
|
UpdateTeamClanNames( TEAM_CT );
|
|
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"clantag\" (value \"%s\")\n",
|
|
pPlayer->GetPlayerName(),
|
|
pPlayer->GetUserID(),
|
|
pPlayer->GetNetworkIDString(),
|
|
pPlayer->GetTeam() ? pPlayer->GetTeam()->GetName() : "UNKNOWN",
|
|
pKeyValues->GetString( "tag", "unknown" ) );
|
|
}
|
|
else if ( 0 == Q_strcmp( pKeyValues->GetName(), "InvalidSteamLogon" ) )
|
|
{
|
|
if ( IsWarmupPeriod() || IsFreezePeriod() || !pPlayer->IsAlive() )
|
|
pKeyValues->SetBool( "disconnect", true );
|
|
else
|
|
pPlayer->m_bInvalidSteamLogonDelayed = true;
|
|
}
|
|
}
|
|
|
|
static bool Helper_CheckFieldAppliesToTeam( char const *szField, int nTeam )
|
|
{
|
|
if ( StringHasPrefix( szField, "#CSGO_MatchInfoTeam_WinAdvan" ) )
|
|
{
|
|
// Make sure that this team is winning
|
|
int nOtherTeam = ( ( nTeam == TEAM_CT ) ? TEAM_TERRORIST : ( ( nTeam == TEAM_TERRORIST ) ? TEAM_CT : nTeam ) );
|
|
return ( GetGlobalTeam( nTeam ) ? GetGlobalTeam( nTeam )->GetScore() : 0 ) > ( GetGlobalTeam( nOtherTeam ) ? GetGlobalTeam( nOtherTeam )->GetScore() : 0 );
|
|
}
|
|
else if ( StringHasPrefix( szField, "#CSGO_MatchInfoTeam_LossElim" ) )
|
|
{
|
|
// Make sure that this team is losing
|
|
int nOtherTeam = ( ( nTeam == TEAM_CT ) ? TEAM_TERRORIST : ( ( nTeam == TEAM_TERRORIST ) ? TEAM_CT : nTeam ) );
|
|
return ( GetGlobalTeam( nTeam ) ? GetGlobalTeam( nTeam )->GetScore() : 0 ) < ( GetGlobalTeam( nOtherTeam ) ? GetGlobalTeam( nOtherTeam )->GetScore() : 0 );
|
|
}
|
|
else if ( ( szField[0] >= '0' ) && ( szField[0] <= '9' ) && ( szField[1] == 0 ) &&
|
|
CSGameRules() && ( ( CSGameRules()->m_match.GetPhase() == GAMEPHASE_HALFTIME ) || ( CSGameRules()->m_match.GetPhase() == GAMEPHASE_MATCH_ENDED ) ) )
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
void CCSGameRules::UpdateTeamPredictions()
|
|
{
|
|
int nWantPrediction = 0;
|
|
if ( ( sm_QueuedServerReservation.pre_match_data().predictions_pct() >= 1 ) &&
|
|
( sm_QueuedServerReservation.pre_match_data().predictions_pct() <= 99 ) )
|
|
nWantPrediction = int( sm_QueuedServerReservation.pre_match_data().predictions_pct() ); // but convar can override
|
|
if ( ( mp_teamprediction_pct.GetInt() >= 1 ) &&
|
|
( mp_teamprediction_pct.GetInt() <= 99 ) )
|
|
nWantPrediction = mp_teamprediction_pct.GetInt();
|
|
|
|
// Prediction UI component cannot be displayed at certain times
|
|
if ( ( m_match.GetPhase() == GAMEPHASE_HALFTIME ) || ( m_match.GetPhase() == GAMEPHASE_MATCH_ENDED ) )
|
|
nWantPrediction = 0;
|
|
|
|
if ( Q_strncmp( m_szTournamentPredictionsTxt, mp_teamprediction_txt.GetString(), MAX_PATH - 1 ) )
|
|
Q_strncpy( m_szTournamentPredictionsTxt.GetForModify(), mp_teamprediction_txt.GetString(), MAX_PATH );
|
|
|
|
char const *szWantMatchStatTxt = mp_teammatchstat_txt.GetString(); // can override from reservation later
|
|
if ( sm_QueuedServerReservation.pre_match_data().stats().size() )
|
|
szWantMatchStatTxt = sm_QueuedServerReservation.pre_match_data().stats( 0 ).match_info_txt().c_str(); // to ensure that it is eligible for a pick
|
|
|
|
//
|
|
// Here we must determine which statistics we are going to be showing
|
|
//
|
|
if ( IsWarmupPeriod() )
|
|
{
|
|
// we are going to show the draft here
|
|
if ( sm_QueuedServerReservation.pre_match_data().stats().size() )
|
|
m_nMatchInfoShowType = 0;
|
|
else if ( *szWantMatchStatTxt )
|
|
m_nMatchInfoShowType = 0;
|
|
else if ( nWantPrediction )
|
|
m_nMatchInfoShowType = k_MapMatchInfoShownCounts_Predictions;
|
|
else
|
|
m_nMatchInfoShowType = k_MapMatchInfoShownCounts_None;
|
|
|
|
m_flMatchInfoDecidedTime = gpGlobals->curtime;
|
|
}
|
|
else if ( IsFreezePeriod() || ( m_iRoundWinStatus != WINNER_NONE ) )
|
|
{
|
|
if ( ( m_nMatchInfoShowType == k_MapMatchInfoShownCounts_None ) ||
|
|
( gpGlobals->curtime - m_flMatchInfoDecidedTime > mp_teammatchstat_cycletime.GetFloat() ) )
|
|
{
|
|
m_nMatchInfoShowType = k_MapMatchInfoShownCounts_None;
|
|
CUtlVectorFixedGrowable< int32, 10 > arrOptions;
|
|
bool bTeamsAreSwitched = AreTeamsPlayingSwitchedSides();
|
|
if ( nWantPrediction )
|
|
arrOptions.AddToTail( k_MapMatchInfoShownCounts_Predictions );
|
|
for ( int j = 0; j < sm_QueuedServerReservation.pre_match_data().stats().size(); ++ j )
|
|
{
|
|
if ( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams().size() < 2 ) continue;
|
|
if ( ( m_match.GetPhase() == GAMEPHASE_MATCH_ENDED ) && ( m_nMatchInfoShowType == k_MapMatchInfoShownCounts_None ) )
|
|
{
|
|
if (
|
|
( ( StringHasPrefix( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(0).c_str(), "#CSGO_MatchInfoTeam_WinAdvan" ) ||
|
|
StringHasPrefix( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(0).c_str(), "#CSGO_MatchInfoTeam_LossElim" ) ) &&
|
|
Helper_CheckFieldAppliesToTeam( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(0).c_str(), bTeamsAreSwitched ? TEAM_TERRORIST : TEAM_CT ) )
|
|
||
|
|
( ( StringHasPrefix( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(1).c_str(), "#CSGO_MatchInfoTeam_WinAdvan" ) ||
|
|
StringHasPrefix( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(1).c_str(), "#CSGO_MatchInfoTeam_LossElim" ) ) &&
|
|
Helper_CheckFieldAppliesToTeam( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(1).c_str(), bTeamsAreSwitched ? TEAM_CT : TEAM_TERRORIST ) )
|
|
)
|
|
{ // always conclude the match with advances/eliminated notification if such is applicable
|
|
m_nMatchInfoShowType = j;
|
|
}
|
|
}
|
|
|
|
if ( Helper_CheckFieldAppliesToTeam( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(0).c_str(), bTeamsAreSwitched ? TEAM_TERRORIST : TEAM_CT ) ||
|
|
Helper_CheckFieldAppliesToTeam( sm_QueuedServerReservation.pre_match_data().stats( j ).match_info_teams(1).c_str(), bTeamsAreSwitched ? TEAM_CT : TEAM_TERRORIST ) )
|
|
{
|
|
arrOptions.AddToTail( j );
|
|
}
|
|
}
|
|
if ( ( !arrOptions.Count() || !sm_QueuedServerReservation.pre_match_data().stats().size() ) && *szWantMatchStatTxt )
|
|
arrOptions.AddToTail( 0 );
|
|
if ( arrOptions.Count() && ( m_nMatchInfoShowType == k_MapMatchInfoShownCounts_None ) )
|
|
{
|
|
// Pick the option that was shown the least number of times
|
|
uint32 nLeastNumberOfTimesShown = ~uint32( 0 );
|
|
FOR_EACH_VEC( arrOptions, iOption )
|
|
{
|
|
MapMatchInfoShownCounts::IndexType_t lookupIdx = m_mapMatchInfoShownCounts.Find( arrOptions[iOption] );
|
|
uint32 nThisNumberOfTimesShown = 0;
|
|
if ( lookupIdx != m_mapMatchInfoShownCounts.InvalidIndex() )
|
|
nThisNumberOfTimesShown = m_mapMatchInfoShownCounts.Element( lookupIdx );
|
|
if ( nThisNumberOfTimesShown < nLeastNumberOfTimesShown )
|
|
{
|
|
nLeastNumberOfTimesShown = nThisNumberOfTimesShown;
|
|
m_nMatchInfoShowType = arrOptions[iOption];
|
|
}
|
|
}
|
|
}
|
|
if ( m_nMatchInfoShowType != k_MapMatchInfoShownCounts_None )
|
|
{
|
|
// Even if some stat was never picked prevent it from showing more than twice in a row
|
|
uint32 numShown = 1;
|
|
MapMatchInfoShownCounts::IndexType_t lookupIdx = m_mapMatchInfoShownCounts.Find( m_nMatchInfoShowType );
|
|
if ( lookupIdx != m_mapMatchInfoShownCounts.InvalidIndex() )
|
|
numShown = m_mapMatchInfoShownCounts.Element( lookupIdx ) + 1;
|
|
FOR_EACH_MAP_FAST( m_mapMatchInfoShownCounts, iSCFast )
|
|
{
|
|
uint32 numOtherShown = m_mapMatchInfoShownCounts.Element( iSCFast );
|
|
if ( numOtherShown > numShown + 1 )
|
|
numShown = numOtherShown - 1;
|
|
}
|
|
|
|
// Track the option that was picked
|
|
m_mapMatchInfoShownCounts.InsertOrReplace( m_nMatchInfoShowType, numShown );
|
|
}
|
|
m_flMatchInfoDecidedTime = gpGlobals->curtime;
|
|
}
|
|
}
|
|
else if ( m_nMatchInfoShowType != k_MapMatchInfoShownCounts_None )
|
|
{
|
|
if ( m_flMatchInfoDecidedTime <= gpGlobals->curtime )
|
|
{ // hold it a little longer to cover brief intermittent gamestate blips
|
|
m_flMatchInfoDecidedTime = gpGlobals->curtime + mp_teammatchstat_holdtime.GetFloat();
|
|
}
|
|
else if ( m_flMatchInfoDecidedTime <= gpGlobals->curtime + 1.0f )
|
|
{ // reset when < 1 sec remaining on hold timer
|
|
m_nMatchInfoShowType = k_MapMatchInfoShownCounts_None;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Kill the desire for things that weren't picked
|
|
//
|
|
if ( m_nMatchInfoShowType != k_MapMatchInfoShownCounts_Predictions )
|
|
nWantPrediction = 0;
|
|
|
|
if ( m_nMatchInfoShowType >= k_MapMatchInfoShownCounts_None )
|
|
szWantMatchStatTxt = "";
|
|
|
|
//
|
|
// Set desired values
|
|
//
|
|
if ( nWantPrediction != m_nTournamentPredictionsPct )
|
|
m_nTournamentPredictionsPct = nWantPrediction;
|
|
|
|
if ( m_nMatchInfoShowType < k_MapMatchInfoShownCounts_None )
|
|
{
|
|
if ( sm_QueuedServerReservation.pre_match_data().stats().size() > m_nMatchInfoShowType )
|
|
{
|
|
int idxMatchTxt = m_nMatchInfoShowType;
|
|
if ( sm_QueuedServerReservation.pre_match_data().stats( m_nMatchInfoShowType ).has_match_info_idxtxt() )
|
|
idxMatchTxt = sm_QueuedServerReservation.pre_match_data().stats( m_nMatchInfoShowType ).match_info_idxtxt();
|
|
szWantMatchStatTxt = sm_QueuedServerReservation.pre_match_data().stats( idxMatchTxt ).match_info_txt().c_str();
|
|
}
|
|
}
|
|
|
|
if ( Q_strncmp( m_szMatchStatTxt, szWantMatchStatTxt, MAX_PATH - 1 ) )
|
|
Q_strncpy( m_szMatchStatTxt.GetForModify(), szWantMatchStatTxt, MAX_PATH );
|
|
}
|
|
|
|
void CCSGameRules::UpdateTeamClanNames( int nTeam )
|
|
{
|
|
Assert( ( nTeam == TEAM_CT ) || ( nTeam == TEAM_TERRORIST ) );
|
|
|
|
CTeam *pTeam = GetGlobalTeam( nTeam );
|
|
//pTeam->SetName( GetDefaultTeamName(nTeam) );
|
|
|
|
bool bTeamsAreSwitched = AreTeamsPlayingSwitchedSides();
|
|
|
|
const char *(pTeamNames[ 2 ]) = { mp_teamname_2.GetString(), mp_teamname_1.GetString() };
|
|
|
|
// If we have a competitive reservation then override team names from it
|
|
if ( ( sm_QueuedServerReservation.tournament_teams().size() > 0 ) &&
|
|
sm_QueuedServerReservation.tournament_teams(0).has_team_name() &&
|
|
* sm_QueuedServerReservation.tournament_teams(0).team_name().c_str() )
|
|
pTeamNames[1] = sm_QueuedServerReservation.tournament_teams(0).team_name().c_str();
|
|
if ( ( sm_QueuedServerReservation.tournament_teams().size() > 1 ) &&
|
|
sm_QueuedServerReservation.tournament_teams(1).has_team_name() &&
|
|
* sm_QueuedServerReservation.tournament_teams(1).team_name().c_str() )
|
|
pTeamNames[0] = sm_QueuedServerReservation.tournament_teams(1).team_name().c_str();
|
|
|
|
int nTeamIndex = ( nTeam - TEAM_TERRORIST ); // nTeamIndex == 0 if Terrorist, 1 if CT
|
|
|
|
const char *pClanName = "";
|
|
uint32 uiClanID = 0;
|
|
|
|
|
|
// Set the team names to the convars depending on what half phase it is.
|
|
if ( !bTeamsAreSwitched )
|
|
pClanName = pTeamNames[ nTeamIndex ];
|
|
else
|
|
pClanName = pTeamNames[ 1 - nTeamIndex ];
|
|
|
|
// The teamname convar was empty so differ to the team's clan name, if it exists.
|
|
if ( StringIsEmpty( pClanName ) && IsClanTeam( pTeam ) )
|
|
{
|
|
for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) );
|
|
if ( pPlayer && !pPlayer->IsBot() )
|
|
{
|
|
pClanName = pPlayer->GetClanName();
|
|
|
|
const char *pClanID = engine->GetClientConVarValue( pPlayer->entindex(), "cl_clanid" );
|
|
uiClanID = Q_atoi( pClanID );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pTeam->SetClanName( pClanName );
|
|
pTeam->SetClanID( uiClanID );
|
|
|
|
//
|
|
// Team flags processing
|
|
//
|
|
const char *pFlag = "";
|
|
const char *(pTeamFlags[ 2 ]) = { mp_teamflag_2.GetString(), mp_teamflag_1.GetString() };
|
|
const char *pLogo = "";
|
|
const char *(pTeamLogos[ 2 ]) = { mp_teamlogo_2.GetString(), mp_teamlogo_1.GetString() };
|
|
int numMapsWon = 0;
|
|
int arrMapsWon[ 2 ] = { mp_teamscore_2.GetInt(), mp_teamscore_1.GetInt() };
|
|
|
|
// If we have a competitive reservation then override team flags from it
|
|
if ( ( sm_QueuedServerReservation.tournament_teams().size() > 0 ) &&
|
|
sm_QueuedServerReservation.tournament_teams(0).has_team_flag() &&
|
|
* sm_QueuedServerReservation.tournament_teams(0).team_flag().c_str() )
|
|
pTeamFlags[1] = sm_QueuedServerReservation.tournament_teams(0).team_flag().c_str();
|
|
|
|
if ( ( sm_QueuedServerReservation.tournament_teams().size() > 1 ) &&
|
|
sm_QueuedServerReservation.tournament_teams(1).has_team_flag() &&
|
|
* sm_QueuedServerReservation.tournament_teams(1).team_flag().c_str() )
|
|
pTeamFlags[0] = sm_QueuedServerReservation.tournament_teams(1).team_flag().c_str();
|
|
|
|
// get the logos
|
|
if ( ( sm_QueuedServerReservation.tournament_teams().size() > 0 ) &&
|
|
sm_QueuedServerReservation.tournament_teams( 0 ).has_team_tag() &&
|
|
* sm_QueuedServerReservation.tournament_teams( 0 ).team_tag().c_str() )
|
|
pTeamLogos[1] = sm_QueuedServerReservation.tournament_teams( 0 ).team_tag().c_str() ;
|
|
|
|
if ( ( sm_QueuedServerReservation.tournament_teams().size() > 01 ) &&
|
|
sm_QueuedServerReservation.tournament_teams( 1 ).has_team_tag() &&
|
|
* sm_QueuedServerReservation.tournament_teams( 1 ).team_tag().c_str() )
|
|
pTeamLogos[0] = sm_QueuedServerReservation.tournament_teams( 1 ).team_tag().c_str();
|
|
|
|
// Set the team names to the convars depending on what half phase it is.
|
|
if ( !bTeamsAreSwitched )
|
|
{
|
|
pFlag = pTeamFlags[ nTeamIndex ];
|
|
pLogo = pTeamLogos[ nTeamIndex ];
|
|
numMapsWon = arrMapsWon[ nTeamIndex ];
|
|
}
|
|
else
|
|
{
|
|
pFlag = pTeamFlags[ 1 - nTeamIndex ];
|
|
pLogo = pTeamLogos[ 1 - nTeamIndex ];
|
|
numMapsWon = arrMapsWon[ 1 - nTeamIndex ];
|
|
}
|
|
|
|
pTeam->SetFlagImageString( pFlag );
|
|
pTeam->SetLogoImageString( pLogo );
|
|
pTeam->SetNumMapVictories( numMapsWon );
|
|
|
|
//
|
|
// Team match stat processing
|
|
//
|
|
const char *pMatchStat = "";
|
|
const char *(pTeamMatchStats[ 2 ]) = { mp_teammatchstat_2.GetString(), mp_teammatchstat_1.GetString() };
|
|
|
|
// If we have a competitive reservation then override team match stats from it
|
|
bool bUsingReservation = false;
|
|
if ( m_nMatchInfoShowType < k_MapMatchInfoShownCounts_None )
|
|
{
|
|
if ( ( sm_QueuedServerReservation.pre_match_data().stats().size() > m_nMatchInfoShowType ) &&
|
|
( sm_QueuedServerReservation.pre_match_data().stats( m_nMatchInfoShowType ).match_info_teams().size() >= 2 ) )
|
|
{
|
|
bUsingReservation = true;
|
|
pTeamMatchStats[1] = sm_QueuedServerReservation.pre_match_data().stats( m_nMatchInfoShowType ).match_info_teams(0).c_str();
|
|
pTeamMatchStats[0] = sm_QueuedServerReservation.pre_match_data().stats( m_nMatchInfoShowType ).match_info_teams(1).c_str();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pTeamMatchStats[0] = pTeamMatchStats[1] = "";
|
|
}
|
|
|
|
// Set the team match stats to the convars depending on what half phase it is.
|
|
if ( !bTeamsAreSwitched )
|
|
pMatchStat = pTeamMatchStats[ nTeamIndex ];
|
|
else
|
|
pMatchStat = pTeamMatchStats[ 1 - nTeamIndex ];
|
|
|
|
if ( bUsingReservation && !Helper_CheckFieldAppliesToTeam( pMatchStat, nTeam ) )
|
|
pMatchStat = "";
|
|
|
|
Q_strncpy( pTeam->m_szTeamMatchStat.GetForModify(), pMatchStat, MAX_PATH );
|
|
}
|
|
|
|
// registered VSCRIPT functions
|
|
void CCSGameRules::SetPlayerCompletedTraining( bool bCompleted )
|
|
{
|
|
|
|
#ifndef CLIENT_DLL
|
|
int nComplete = 0;
|
|
if ( bCompleted )
|
|
nComplete = 1;
|
|
|
|
tr_completed_training.SetValue( nComplete );
|
|
#endif
|
|
}
|
|
|
|
bool CCSGameRules::GetPlayerCompletedTraining( void )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
return tr_completed_training.GetBool();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void CCSGameRules::SetBestTrainingCourseTime( int nTime )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
tr_best_course_time.SetValue( nTime );
|
|
#endif
|
|
}
|
|
|
|
int CCSGameRules::GetBestTrainingCourseTime( void )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
return tr_best_course_time.GetInt();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int CCSGameRules::GetValveTrainingCourseTime( void )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
return tr_valve_course_time.GetInt();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
bool CCSGameRules::IsLocalPlayerUsingController( void )
|
|
{
|
|
CCSPlayer *pPlayer = static_cast<CCSPlayer*>( UTIL_PlayerByIndex(1) );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
if ( pPlayer->GetPlayerInputDevice() == INPUT_DEVICE_GAMEPAD )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CCSGameRules::TrainingGivePlayerAmmo( void )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
|
|
|
|
if ( pPlayer )
|
|
{
|
|
CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon();
|
|
|
|
if( pWeapon && pWeapon->UsesPrimaryAmmo() )
|
|
{
|
|
pWeapon->SetReserveAmmoCount( AMMO_POSITION_PRIMARY, pWeapon->GetReserveAmmoMax( AMMO_POSITION_PRIMARY ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::TrainingSetRadarHidden( bool bHide )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
|
|
|
|
if ( pPlayer )
|
|
{
|
|
CCSPlayer *pCSPlayer = static_cast<CCSPlayer *>( pPlayer );
|
|
if ( pCSPlayer )
|
|
pCSPlayer->SetRadarHidden( bHide );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::TrainingSetMiniScoreHidden( bool bHide )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
|
|
|
|
if ( pPlayer )
|
|
{
|
|
CCSPlayer *pCSPlayer = static_cast<CCSPlayer *>( pPlayer );
|
|
if ( pCSPlayer )
|
|
pCSPlayer->SetMiniScoreHidden( bHide );
|
|
}
|
|
}
|
|
|
|
// not used yet because this relied on vgui panels, scaleform isn't supported yet
|
|
void CCSGameRules::TrainingHighlightAmmoCounter( void )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
|
|
|
|
if ( pPlayer )
|
|
{
|
|
|
|
#ifndef CLIENT_DLL
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "tr_highlight_ammo", true );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", pPlayer->GetUserID() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::TrainingShowFinishMsgBox( void )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
|
|
|
|
if ( pPlayer )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "tr_show_finish_msgbox", true );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::TrainingShowExitDoorMsg( void )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
|
|
|
|
if ( pPlayer )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "tr_show_exit_msgbox", true );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptCoopResetRoundStartTime( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
pRules->CoopResetRoundStartTime();
|
|
}
|
|
|
|
void ScriptCoopGiveC4sToCTs( int nNumC4ToGive )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
pRules->CoopGiveC4sToCTs( nNumC4ToGive );
|
|
}
|
|
|
|
void ScriptSetPlayerCompletedTraining( bool bCompleted )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
pRules->SetPlayerCompletedTraining( bCompleted );
|
|
}
|
|
|
|
bool ScriptGetPlayerCompletedTraining( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return false;
|
|
|
|
return pRules->GetPlayerCompletedTraining();
|
|
}
|
|
|
|
void ScriptSetBestTrainingCourseTime( int nTime )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
pRules->SetBestTrainingCourseTime( nTime );
|
|
}
|
|
|
|
int ScriptGetBestTrainingCourseTime( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return false;
|
|
|
|
return pRules->GetBestTrainingCourseTime();
|
|
}
|
|
|
|
int ScriptGetValveTrainingCourseTime( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return false;
|
|
|
|
return pRules->GetValveTrainingCourseTime();
|
|
}
|
|
|
|
void ScriptTrainingGivePlayerAmmo( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules || !pRules->IsPlayingTraining() )
|
|
return;
|
|
|
|
pRules->TrainingGivePlayerAmmo();
|
|
}
|
|
|
|
void ScriptSetMiniScoreHidden( bool nHide )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules || !pRules->IsPlayingTraining() )
|
|
return;
|
|
|
|
pRules->TrainingSetMiniScoreHidden( nHide );
|
|
}
|
|
|
|
void ScriptSetRadarHidden( bool nHide )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules || !pRules->IsPlayingTraining() )
|
|
return;
|
|
|
|
pRules->TrainingSetRadarHidden( nHide );
|
|
}
|
|
|
|
void ScriptHighlightAmmoCounter( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules || !pRules->IsPlayingTraining() )
|
|
return;
|
|
|
|
pRules->TrainingHighlightAmmoCounter();
|
|
}
|
|
|
|
void ScriptShowFinishMsgBox( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules || !pRules->IsPlayingTraining() )
|
|
return;
|
|
|
|
pRules->TrainingShowFinishMsgBox();
|
|
}
|
|
|
|
void ScriptShowExitDoorMsg( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules || !pRules->IsPlayingTraining() )
|
|
return;
|
|
|
|
pRules->TrainingShowExitDoorMsg();
|
|
}
|
|
|
|
bool ScriptIsLocalPlayerUsingController( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return false;
|
|
|
|
return pRules->IsLocalPlayerUsingController();
|
|
}
|
|
|
|
void ScriptPrintMessageCenterAll( const char *pszMessage )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, pszMessage );
|
|
}
|
|
|
|
void ScriptPrintMessageChatAll( const char *pszMessage )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTTALK, pszMessage );
|
|
}
|
|
|
|
void ScriptPrintMessageCenterTeam( int nTeamNumber, const char *pszMessage )
|
|
{
|
|
CRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
filter.AddRecipientsByTeam( GetGlobalTeam(nTeamNumber) );
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTCENTER, pszMessage );
|
|
}
|
|
|
|
void ScriptPrintMessageChatTeam( int nTeamNumber, const char *pszMessage )
|
|
{
|
|
CRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
filter.AddRecipientsByTeam( GetGlobalTeam(nTeamNumber) );
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, pszMessage );
|
|
}
|
|
|
|
int ScriptGetGameMode( void )
|
|
{
|
|
return g_pGameTypes->GetCurrentGameMode();
|
|
}
|
|
|
|
int ScriptGetGameType( void )
|
|
{
|
|
return g_pGameTypes->GetCurrentGameType();
|
|
}
|
|
|
|
|
|
int ScriptGetRoundsPlayed( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return false;
|
|
|
|
return pRules->GetRoundsPlayed();
|
|
}
|
|
|
|
bool ScriptIsWarmupPeriod( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return false;
|
|
|
|
return pRules->IsWarmupPeriod();
|
|
}
|
|
|
|
void ScriptCoopSetBotQuotaAndRefreshSpawns( int nMaxEnemiesToSpawn )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
return pRules->CoopSetBotQuotaAndRefreshSpawns( nMaxEnemiesToSpawn );
|
|
}
|
|
|
|
void ScriptCoopMissionSpawnFirstEnemies( int nMaxEnemiesToSpawn )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
return pRules->CoopMissionSpawnFirstEnemies( nMaxEnemiesToSpawn );
|
|
}
|
|
|
|
void ScriptCoopMissionSetNextRespawnIn( float flSeconds, bool bIncrementWaveNumber )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
return pRules->CoopMissionSetNextRespawnIn( flSeconds, bIncrementWaveNumber );
|
|
}
|
|
|
|
void ScriptCoopMissionSpawnNextWave( int nMaxEnemiesToSpawn )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
return pRules->CoopMissionSpawnNextWave( nMaxEnemiesToSpawn );
|
|
}
|
|
|
|
void ScriptCoopMissionRespawnDeadPlayers( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
return pRules->CoopMissionRespawnDeadPlayers();
|
|
}
|
|
|
|
int ScriptCoopMissionGetMissionNumber( void )
|
|
{
|
|
return mp_coopmission_mission_number.GetInt();
|
|
}
|
|
|
|
void ScriptCoopCollectBonusCoin( void )
|
|
{
|
|
CCSGameRules *pRules = CSGameRules();
|
|
if ( !pRules )
|
|
return;
|
|
|
|
return pRules->CoopCollectBonusCoin();
|
|
}
|
|
|
|
void CCSGameRules::RegisterScriptFunctions( void )
|
|
{
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptSetPlayerCompletedTraining, "Sets whether the player has completed the initial portion of the training map." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptGetPlayerCompletedTraining, "Returns true if the player has completed the initial portion of the training map." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptSetBestTrainingCourseTime, "Sets the player's best time for completing the timed course." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptGetBestTrainingCourseTime, "Gets the player's best time for completing the timed course." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptGetValveTrainingCourseTime, "Gets Valve's best time for completing the timed course." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptTrainingGivePlayerAmmo, "Refills ammo to max for all weapons the player has (only works in training)." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptSetMiniScoreHidden, "Toggles the visibility of the miniscoreboard hud element." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptSetRadarHidden, "Toggles the visibility of the radar hud element." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptHighlightAmmoCounter, "Sends an event that is just used by the instructor system to show a hint highlighting the ammo counter." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptShowFinishMsgBox, "Shows a message box to let players know what to do next after finishing the training course." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptShowExitDoorMsg, "Shows a message box in trainign when the player exits through the exit door" );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptIsLocalPlayerUsingController, "Returns whether the player is playing with a controller or not." );
|
|
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptPrintMessageCenterAll, "Prints an alert message in the center print method to all players." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptPrintMessageChatAll, "Prints a message in chat to all players." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptPrintMessageCenterTeam, "Prints an alert message in the center print method to the specified team." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptPrintMessageChatTeam, "Prints a message in chat to the specified team." );
|
|
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptGetGameMode, "Gets the current game mode." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptGetGameType, "Gets the current game type." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptGetRoundsPlayed, "Get the number of rounds played so far." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptIsWarmupPeriod, "Is it warmup or not." );
|
|
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopSetBotQuotaAndRefreshSpawns, "Sets the bot quota considering the # of players connected and refreshes the spawns." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopMissionSpawnFirstEnemies, "Spawns the first wave of enemies in coop." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopMissionSetNextRespawnIn, "Set the next respawn wave to happen in this many seconds." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopMissionSpawnNextWave, "Tells the next wave of enemies to spawn in coop. Also respawns player." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopMissionRespawnDeadPlayers, "Respawns players only." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopMissionGetMissionNumber, "Gets the mission number for the current map - maps can have multiple missions on them." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopResetRoundStartTime, "Resets the round time and starts the mission." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopGiveC4sToCTs, "Will give the number of specified C4s to all alive CT players." );
|
|
ScriptRegisterFunction( g_pScriptVM, ScriptCoopCollectBonusCoin, "Marks one of the bonus coins as collected." );
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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" ) )
|
|
{
|
|
// catch corrupted command
|
|
if ( pPlayer == NULL )
|
|
return true;
|
|
|
|
extern ConVar nextmap_print_enabled;
|
|
if ( !nextmap_print_enabled.GetBool() )
|
|
return true;
|
|
|
|
if ( pPlayer->m_iNextTimeCheck < gpGlobals->curtime )
|
|
{
|
|
char szNextMap[MAX_PATH];
|
|
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
|
|
{
|
|
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
|
|
}
|
|
else
|
|
{
|
|
GetNextLevelName( szNextMap, sizeof( szNextMap ) );
|
|
}
|
|
|
|
if ( IsGameConsole() || engine->IsDedicatedServerForXbox() || engine->IsDedicatedServerForPS3() )
|
|
{
|
|
// we know all the map names on console so we can safely assume we have a token for all maps
|
|
const int nMaxLength = MAX_MAP_NAME+10+1;
|
|
char szToken[nMaxLength];
|
|
CreateFriendlyMapNameToken(szNextMap, szToken, nMaxLength);
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", szToken );
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", V_GetFileName( szNextMap ) );
|
|
}
|
|
|
|
pPlayer->m_iNextTimeCheck = gpGlobals->curtime + 1;
|
|
}
|
|
return true;
|
|
}
|
|
else if ( FStrEq( args[0], "tr_map_show_exit_door_msg" ) )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "tr_exit_hint_trigger" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
//engine->ServerCommand( "ent_fire @tr_exit_hint trigger\n" );
|
|
return true;
|
|
}
|
|
else if( pPlayer != NULL && 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::PlayerSpawn( CBasePlayer *pBasePlayer )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer );
|
|
if ( !pPlayer )
|
|
Error( "PlayerSpawn" );
|
|
|
|
if ( pPlayer->State_Get() != STATE_ACTIVE )
|
|
return;
|
|
|
|
if ( CSGameRules()->IsPlayingCoopGuardian()/* || CSGameRules()->IsPlayingCoopMission()*/ )
|
|
CSGameRules()->GuardianUpdateBotAccountAndWeapons( pPlayer );
|
|
|
|
pPlayer->EquipSuit();
|
|
|
|
// remove any defusers left over from previous random if there is just one random one
|
|
if ( mp_defuser_allocation.GetInt() == DefuserAllocation::Random )
|
|
pPlayer->RemoveDefuser();
|
|
|
|
bool addDefault = (pPlayer->GetTeamNumber() > TEAM_SPECTATOR);
|
|
|
|
CBaseEntity *pWeaponEntity = NULL;
|
|
while ( ( pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL )
|
|
{
|
|
CGamePlayerEquip *pEquip = dynamic_cast<CGamePlayerEquip*>( pWeaponEntity );
|
|
if ( pEquip && !pEquip->UseOnly() )
|
|
{
|
|
if ( addDefault && pEquip->StripFirst() )
|
|
{
|
|
// remove all our weapons and armor before touching the first game_player_equip
|
|
pPlayer->RemoveAllItems( true );
|
|
}
|
|
pWeaponEntity->Touch( pPlayer );
|
|
addDefault = false;
|
|
}
|
|
}
|
|
|
|
if ( addDefault )
|
|
pPlayer->GiveDefaultItems();
|
|
if ( !IsWarmupPeriod() && IsPlayingCooperativeGametype() && !IsQueuedMatchmaking() )
|
|
{
|
|
switch ( pBasePlayer->GetTeamNumber() )
|
|
{
|
|
case TEAM_CT:
|
|
case TEAM_TERRORIST:
|
|
// Ensure that all QMM structures are always initialized for players in coop game type
|
|
( void ) QueuedMatchmakingPlayersDataFindOrCreate( pPlayer );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::BroadcastSound( const char *sound, int team )
|
|
{
|
|
CBroadcastRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
|
|
if( team != -1 )
|
|
{
|
|
filter.RemoveAllRecipients();
|
|
filter.AddRecipientsByTeam( GetGlobalTeam(team) );
|
|
}
|
|
|
|
CCSUsrMsg_SendAudio msg;
|
|
msg.set_radio_sound( sound ) ;
|
|
SendUserMessage( filter, CS_UM_SendAudio, msg );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
|
|
// [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 = dynamic_cast< CHEGrenadeProjectile* >( pInflictor ) != NULL;
|
|
bool isBomb = pInflictor && V_strcmp(pInflictor->GetClassname(), "planted_c4") == 0;
|
|
|
|
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 ) & 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() )
|
|
{
|
|
// [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();
|
|
}
|
|
|
|
bool bIgnoreDamageToThisEntity = ( pEntity->m_takedamage == DAMAGE_NO ) ||
|
|
( pCSExplosionVictim && !wasAliveBeforeExplosion );
|
|
|
|
if ( !bIgnoreDamageToThisEntity )
|
|
{
|
|
// 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 used to not travel into or out of water, users assumed it was a bug. Fix is not to run this check -wills
|
|
//if ( !bIgnoreWorld )
|
|
//{
|
|
// if (bInWater && pEntity->GetWaterLevel() == WL_NotInWater)
|
|
// continue;
|
|
// if (!bInWater && pEntity->GetWaterLevel() == WL_Eyes)
|
|
// 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.SetRadius( flRadius );
|
|
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 );
|
|
|
|
// [sbodenbender] Increment grenade damage stat
|
|
if (pCSExplosionVictim &&
|
|
pCSExplosionAttacker &&
|
|
isGrenade &&
|
|
pCSExplosionVictim->GetTeamNumber() != pCSExplosionAttacker->GetTeamNumber() )
|
|
{
|
|
CCS_GameStats.IncrementStat(pCSExplosionAttacker, CSSTAT_GRENADE_DAMAGE, static_cast<int>(adjustedInfo.GetDamage()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// [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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [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 );
|
|
}
|
|
}
|
|
|
|
CCSPlayer* CCSGameRules::CheckAndAwardAssists( CCSPlayer* pCSVictim, CCSPlayer* pKiller )
|
|
{
|
|
CUtlLinkedList< CDamageRecord *, int >& victimDamageTakenList = pCSVictim->GetDamageList();
|
|
float maxDamage = 0.0f;
|
|
CCSPlayer* maxDamagePlayer = NULL;
|
|
|
|
FOR_EACH_LL( victimDamageTakenList, ii )
|
|
{
|
|
if ( victimDamageTakenList[ii]->GetPlayerRecipientPtr() == pCSVictim )
|
|
{
|
|
CCSPlayer* pAttackerPlayer = victimDamageTakenList[ii]->GetPlayerDamagerPtr();
|
|
if ( pAttackerPlayer )
|
|
{
|
|
if ( (victimDamageTakenList[ii]->GetDamage() > maxDamage) && (pAttackerPlayer != pKiller) && ( pAttackerPlayer != pCSVictim ) )
|
|
{
|
|
maxDamage = victimDamageTakenList[ii]->GetDamage();
|
|
maxDamagePlayer = pAttackerPlayer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// note, only the highest damaging player can be awarded an assist
|
|
if ( maxDamagePlayer && (maxDamage > cs_AssistDamageThreshold.GetFloat( )) )
|
|
{
|
|
ScorePlayerAssist( maxDamagePlayer, pCSVictim );
|
|
return maxDamagePlayer;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pVictim -
|
|
// *pKiller -
|
|
// *pInflictor -
|
|
//-----------------------------------------------------------------------------
|
|
IGameEvent * CCSGameRules::CreateWeaponKillGameEvent( char const *szEventName, const CTakeDamageInfo &info )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( szEventName );
|
|
if ( !event )
|
|
return NULL;
|
|
|
|
// Work out what killed the player, and prepare to 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;
|
|
char killer_XUID[64] = { '\0' };
|
|
char killer_weapon_itemid[64] = { '\0' };
|
|
char killer_weapon_fauxitemid[64] = { '\0' };
|
|
char kill_weapon_originalowner_xuid[64] = { '\0' };
|
|
|
|
|
|
// Find the killer & the scorer
|
|
CBaseEntity *pInflictor = info.GetInflictor();
|
|
CBaseEntity *pKiller = info.GetAttacker();
|
|
CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor );
|
|
|
|
bool bHeadshot = false;
|
|
|
|
if ( pScorer ) // Is the killer a client?
|
|
{
|
|
killer_ID = pScorer->GetUserID();
|
|
|
|
V_sprintf_safe( killer_XUID, "%llu", pScorer->GetSteamIDAsUInt64() );
|
|
|
|
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() )
|
|
{
|
|
CEconItemView *pItem = pScorer->GetActiveWeapon()->GetEconItemView();
|
|
|
|
killer_weapon_name = ( ( pItem && pItem->IsValid() && pItem->GetItemIndex() && pItem->GetItemDefinition() )
|
|
? pItem->GetItemDefinition()->GetDefinitionName()
|
|
: pScorer->GetActiveWeapon()->GetClassname() ); //GetDeathNoticeName();
|
|
|
|
if ( pItem && pItem->IsValid() )
|
|
{
|
|
V_sprintf_safe( killer_weapon_itemid, "%llu", pItem->GetItemID() );
|
|
|
|
V_sprintf_safe( killer_weapon_fauxitemid, "%llu", CombinedItemIdMakeFromDefIndexAndPaint( pItem->GetItemDefinition()->GetDefinitionIndex(), pItem->GetCustomPaintKitIndex() ) );
|
|
}
|
|
|
|
//the default weapon knife looks different in the kill feed depending on faction
|
|
if ( !V_strcmp( killer_weapon_name, "weapon_knife" ) )
|
|
{
|
|
killer_weapon_name = "weapon_knife_default_t";
|
|
if ( pScorer->GetTeamNumber() == TEAM_CT )
|
|
killer_weapon_name = "weapon_knife_default_ct";
|
|
}
|
|
|
|
}
|
|
}
|
|
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 ( IsWeaponClassname( killer_weapon_name ) )
|
|
{
|
|
killer_weapon_name += WEAPON_CLASSNAME_PREFIX_LENGTH;
|
|
}
|
|
else if ( StringHasPrefix( killer_weapon_name, "NPC_" ) )
|
|
{
|
|
killer_weapon_name += V_strlen( "NPC_" );
|
|
}
|
|
else if ( StringHasPrefixCaseSensitive( killer_weapon_name, "func_" ) )
|
|
{
|
|
killer_weapon_name += V_strlen( "func_" );
|
|
}
|
|
else if( StringHasPrefixCaseSensitive( killer_weapon_name, "hegrenade" ) ) //"hegrenade_projectile"
|
|
{
|
|
killer_weapon_name = "hegrenade";
|
|
}
|
|
else if( StringHasPrefixCaseSensitive( killer_weapon_name, "flashbang" ) ) //"flashbang_projectile"
|
|
{
|
|
killer_weapon_name = "flashbang";
|
|
}
|
|
else if( StringHasPrefixCaseSensitive( killer_weapon_name, "decoy" ) ) //"decoy_projectile"
|
|
{
|
|
killer_weapon_name = "decoy";
|
|
}
|
|
else if( StringHasPrefixCaseSensitive( killer_weapon_name, "smokegrenade" ) ) //"smokegrenade_projectile"
|
|
{
|
|
killer_weapon_name = "smokegrenade";
|
|
}
|
|
|
|
|
|
event->SetInt("attacker", killer_ID );
|
|
event->SetString("weapon", killer_weapon_name );
|
|
event->SetString("weapon_itemid", killer_weapon_itemid );
|
|
event->SetString("weapon_fauxitemid", killer_weapon_fauxitemid );
|
|
event->SetBool("headshot", bHeadshot );
|
|
event->SetString("weapon_originalowner_xuid", kill_weapon_originalowner_xuid );
|
|
|
|
// If the weapon has a silencer but it isn't currently attached, add "_off" suffix to the weapon name so hud can find an alternate icon
|
|
if ( pInflictor && pScorer && (pInflictor == pScorer) )
|
|
{
|
|
CWeaponCSBase* pWeapon = dynamic_cast< CWeaponCSBase* >( pScorer->GetActiveWeapon() );
|
|
if ( pWeapon && pWeapon->HasSilencer() && !pWeapon->IsSilenced() )
|
|
{
|
|
if ( V_strEndsWith( killer_weapon_name, "silencer" ) )
|
|
{
|
|
char szTempWeaponNameWithOFFsuffix[64];
|
|
V_snprintf( szTempWeaponNameWithOFFsuffix, sizeof(szTempWeaponNameWithOFFsuffix), "%s_off", killer_weapon_name );
|
|
event->SetString("weapon", szTempWeaponNameWithOFFsuffix );
|
|
}
|
|
}
|
|
}
|
|
|
|
event->SetInt( "penetrated", info.GetObjectsPenetrated() );
|
|
|
|
return event;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pVictim -
|
|
// *pKiller -
|
|
// *pInflictor -
|
|
//-----------------------------------------------------------------------------
|
|
void CCSGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info )
|
|
{
|
|
// Find the killer & the scorer
|
|
CBaseEntity *pInflictor = info.GetInflictor();
|
|
CBaseEntity *pKiller = info.GetAttacker();
|
|
CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor );
|
|
CCSPlayer *pCSVictim = (CCSPlayer*)(pVictim);
|
|
CCSPlayer *pAssiter = CheckAndAwardAssists( pCSVictim, (CCSPlayer *)pKiller );
|
|
|
|
if ( IGameEvent * event = CreateWeaponKillGameEvent( "player_death", info ) )
|
|
{
|
|
event->SetInt("userid", pVictim->GetUserID() );
|
|
event->SetInt("assister", pAssiter ? pAssiter->GetUserID() : 0 );
|
|
if( !OnReplayPrompt( pVictim, pScorer ) )
|
|
event->SetBool( "noreplay", true ); // prevent UI display of "press to replay" message if HLTV data is unavailable
|
|
|
|
bool bHeadshot = ( pScorer ) && ( info.GetDamageType() & DMG_HEADSHOT );
|
|
int priority = bHeadshot ? 8 : 7;
|
|
#ifdef GAME_DLL
|
|
CCSPlayer* player = ( dynamic_cast< CCSPlayer* >(pScorer) );
|
|
|
|
if ( player )
|
|
{
|
|
priority += player->GetKillStreak();
|
|
}
|
|
#endif
|
|
event->SetInt( "priority", priority ); // player_death
|
|
|
|
if ( pCSVictim->GetDeathFlags() & CS_DEATH_DOMINATION )
|
|
{
|
|
event->SetInt( "dominated", 1 );
|
|
}
|
|
else if ( pCSVictim->GetDeathFlags() & CS_DEATH_REVENGE )
|
|
{
|
|
event->SetInt( "revenge", 1 );
|
|
}
|
|
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
}
|
|
|
|
bool EconEntity_OnOwnerKillEaterEvent( CEconItemView *pEconItemView, CCSPlayer *pOwner, CCSPlayer *pVictim, kill_eater_event_t eEventType, int iAmount /*= 1*/, uint32 *pNewValue /* = NULL */ )
|
|
{
|
|
if ( pNewValue )
|
|
*pNewValue = 0;
|
|
|
|
// Kill-eater weapons.
|
|
if ( !pEconItemView )
|
|
return false;
|
|
|
|
if ( !pOwner )
|
|
return false;
|
|
|
|
// Ignore events where we're affecting ourself.
|
|
if ( pOwner == pVictim )
|
|
return false;
|
|
|
|
// Always require that we have at least the base kill eater attribute before sending any messages
|
|
// to the GC.
|
|
static CSchemaAttributeDefHandle pAttr_KillEater( "kill eater" );
|
|
if ( !pEconItemView->FindAttribute( pAttr_KillEater ) )
|
|
return false;
|
|
|
|
// Don't bother sending a message to the GC if either party is a bot, unless we're tracking events against
|
|
// bots specifically.
|
|
CSteamID KillerSteamID, VictimSteamID;
|
|
if ( !pOwner->GetSteamID( &KillerSteamID ) )
|
|
return false;
|
|
|
|
// comment this out to have StatTrak count bot kills
|
|
if ( pVictim && !pVictim->GetSteamID( &VictimSteamID ) )
|
|
return false;
|
|
|
|
// we don't want to increment the killeater if the weapon owner is not the killer
|
|
if ( pEconItemView->GetAccountID() != KillerSteamID.GetAccountID() )
|
|
return false;
|
|
|
|
// comment this out to have StatTrak count bot kills
|
|
if ( pVictim && pVictim->IsFakeClient() )
|
|
return false;
|
|
|
|
// Also require that we have whatever event type we're looking for, unless we're looking for regular
|
|
// player kills in which case we may or may not have a field to describe that.
|
|
|
|
AssertMsg( GetKillEaterAttrPairCount() == 1, "This function is now assuming there is only one stattrak value being updated when called. If we're adding multiple killeaters per item, this needs to be revisited." );
|
|
const CEconItemAttributeDefinition *pScoreAttribDef = GetKillEaterAttrPair_Score(0);
|
|
if ( !pScoreAttribDef )
|
|
return false;
|
|
|
|
// If we don't have this attribute, move on. It's possible to be missing this attribute but still
|
|
// have the next one in the list if we have user-customized tracking types.
|
|
uint32 unCurrent;
|
|
if ( !FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItemView, pScoreAttribDef, &unCurrent ) )
|
|
return false;
|
|
|
|
const CEconItemAttributeDefinition *pScoreTypeAttribDef = GetKillEaterAttrPair_Type(0);
|
|
if ( !pScoreTypeAttribDef )
|
|
return false;
|
|
|
|
unCurrent += iAmount;
|
|
pEconItemView->UpdateNetworkedDynamicAttributesForDemos( pScoreAttribDef->GetDefinitionIndex(), *(float*)&unCurrent );
|
|
|
|
if ( pNewValue )
|
|
*pNewValue = unCurrent;
|
|
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
|
|
|
|
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;
|
|
|
|
// determine whether this is a positive or negative kill
|
|
int numPointsForPlayerKill = IPointsForKill( pScorer, pVictim );
|
|
CWeaponCSBase* pWeapon = dynamic_cast<CWeaponCSBase *>( info.GetWeapon() );
|
|
|
|
// [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 );
|
|
|
|
pCSVictim->SetLastKillerIndex( pCSScorer ? pCSScorer->entindex() : 0 );
|
|
|
|
// 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 );
|
|
|
|
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" [%.0f %.0f %.0f] was killed by the bomb.\n",
|
|
pCSVictim->GetPlayerName(),
|
|
pCSVictim->GetUserID(),
|
|
pCSVictim->GetNetworkIDString(),
|
|
pCSVictim->GetTeam()->GetName(),
|
|
pCSVictim->GetAbsOrigin().x,
|
|
pCSVictim->GetAbsOrigin().y,
|
|
pCSVictim->GetAbsOrigin().z );
|
|
}
|
|
else
|
|
{
|
|
if ( ( !pCSScorer && !dynamic_cast< CBaseGrenade * >( pInflictor ) && !dynamic_cast< CInferno * >( pInflictor ) ) || // special case for suicide tracking: exclude grenade exploding when thrower disconnected
|
|
( pCSVictim == pCSScorer ) )
|
|
{
|
|
if ( UseSuicidePenalty() &&
|
|
!pCSVictim->IsControllingBot() &&
|
|
mp_autokick.GetBool() &&
|
|
!CSGameRules()->IsPlayingOffline() &&
|
|
!CSGameRules()->IsWarmupPeriod() &&
|
|
( pVictim->GetPendingTeamNumber() == pVictim->GetTeamNumber() ) ) // don't punish suicides due to team change
|
|
{
|
|
pVictim->m_nSuicides++;
|
|
|
|
if ( pVictim->m_nSuicides >= 5 )
|
|
{
|
|
if ( sv_kick_ban_duration.GetInt() > 0 )
|
|
{
|
|
engine->ServerCommand( CFmtStr( "banid %d %d;", sv_kick_ban_duration.GetInt(), pVictim->GetUserID() ) );
|
|
}
|
|
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid_ex %d %d For suiciding too many times\n", pVictim->GetUserID(), 1 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pCSVictim == pCSScorer )
|
|
{
|
|
CSGameRules()->ScorePlayerSuicide( pCSVictim );
|
|
}
|
|
else if ( numPointsForPlayerKill >= 0 )
|
|
{
|
|
// Track kills per weapon before increment frag count call
|
|
if ( pWeapon && pCSScorer )
|
|
{
|
|
switch ( pWeapon->GetWeaponType() )
|
|
{
|
|
case WEAPONTYPE_PISTOL:
|
|
{
|
|
int numPistolKills, numSniperKills;
|
|
pCSScorer->GetEnemyWeaponKills( numPistolKills, numSniperKills );
|
|
pCSScorer->SetEnemyWeaponKills( numPistolKills + 1, numSniperKills );
|
|
}
|
|
break;
|
|
case WEAPONTYPE_SNIPER_RIFLE:
|
|
{
|
|
int numPistolKills, numSniperKills;
|
|
pCSScorer->GetEnemyWeaponKills( numPistolKills, numSniperKills );
|
|
pCSScorer->SetEnemyWeaponKills( numPistolKills, numSniperKills + 1 );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
BaseClass::PlayerKilled( pVictim, info );
|
|
}
|
|
|
|
// check for team-killing, and give monetary rewards/penalties
|
|
// Find the killer & the scorer
|
|
if ( !pScorer )
|
|
{
|
|
pCSVictim->SetLastConcurrentKilled( 0 );
|
|
return;
|
|
}
|
|
|
|
if ( numPointsForPlayerKill < 0 )
|
|
{
|
|
// team-killer!
|
|
pCSScorer->AddAccountAward( PlayerCashAward::KILL_TEAMMATE );
|
|
// m_DeferredCallQueue.QueueCall( pCSScorer, &CCSPlayer::AddAccountAward, PlayerCashAward::KILL_TEAMMATE );
|
|
pCSScorer->IncrementTeamKillsCount( 1 );
|
|
pCSScorer->m_bJustKilledTeammate = true;
|
|
|
|
ClientPrint( pCSScorer, HUD_PRINTCENTER, "#SFUI_Notice_Killed_Teammate" );
|
|
if ( mp_autokick.GetBool() )
|
|
{
|
|
if ( !IsPlayingOffline() )
|
|
{
|
|
char strTeamKills[64];
|
|
Q_snprintf( strTeamKills, sizeof( strTeamKills ), "%d", (3 - pCSScorer->m_iTeamKills) );
|
|
ClientPrint( pCSScorer, HUD_PRINTTALK, "#SFUI_Notice_Game_teammate_kills", strTeamKills ); // this includes a " of 3" in it
|
|
}
|
|
|
|
if ( pCSScorer->m_iTeamKills >= 3 )
|
|
{
|
|
if ( !IsPlayingOffline() )
|
|
{
|
|
ClientPrint( pCSScorer, HUD_PRINTTALK, "#SFUI_Notice_Banned_For_Killing_Teammates" );
|
|
if ( sv_kick_ban_duration.GetInt() > 0 )
|
|
{
|
|
SendKickBanToGC( pCSScorer, k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_TKLimit );
|
|
// don't roll the kick command into this, it will fail on a lan, where kickid will go through
|
|
engine->ServerCommand( CFmtStr( "banid %d %d;", sv_kick_ban_duration.GetInt(), pCSScorer->GetUserID() ) );
|
|
}
|
|
}
|
|
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid_ex %d %d For killing too many teammates\n", pCSScorer->GetUserID(), IsPlayingOffline() ? 0 : 1 ) );
|
|
}
|
|
else if ( ( mp_spawnprotectiontime.GetInt() > 0 ) && ( GetRoundElapsedTime() < mp_spawnprotectiontime.GetInt() ) )
|
|
{
|
|
const CCSWeaponInfo* pWeaponInfo = CCSPlayer::GetWeaponInfoFromDamageInfo( info );
|
|
|
|
// Special case here: TK at round start using some weapons* only counts towards team damage
|
|
// and team kills amount, but doesn't instantly ban the player due to common occurrence
|
|
// of teammates crossing in front of AWP'er on de_dust2
|
|
// Weapons that will not instantly ban for TK at round start must have zoom levels and
|
|
// team damage from headshot must kill with a single kill
|
|
extern ConVar ff_damage_reduction_bullets;
|
|
if ( !pWeaponInfo || !pWeapon ||
|
|
( pWeapon->GetZoomLevels() < 2 ) || // weapon doesn't have 2 zoom levels, worth the ban
|
|
( float( pWeapon->GetDamage() ) * 4.0f * ff_damage_reduction_bullets.GetFloat() <= 99.0f ) || // single friendly headshot will not kill, worth the ban
|
|
!pCSVictim || ( pCSVictim->GetNumAttackersFromDamageList() > 1 ) || // victim already took damage from multiple players, TK should be banned
|
|
( pCSVictim->GetMostNumHitsDamageRecordFrom(pCSScorer) > 1 ) // TK'er shot the victim multiple times, worth the ban
|
|
)
|
|
{
|
|
if ( !IsPlayingOffline() )
|
|
{
|
|
ClientPrint( pCSScorer, HUD_PRINTTALK, "#SFUI_Notice_Banned_For_TK_Start" );
|
|
if ( sv_kick_ban_duration.GetInt() > 0 )
|
|
{
|
|
SendKickBanToGC( pCSScorer, k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_TKSpawn );
|
|
// don't roll the kick command into this, it will fail on a lan, where kickid will go through
|
|
engine->ServerCommand( CFmtStr( "banid %d %d;", sv_kick_ban_duration.GetInt(), pCSScorer->GetUserID() ) );
|
|
}
|
|
}
|
|
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid_ex %d %d For killing a teammate at round start\n", pCSScorer->GetUserID(), IsPlayingOffline() ? 0 : 1 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_FRIEND_KILLED) )
|
|
{
|
|
pCSScorer->m_iDisplayHistoryBits |= DHF_FRIEND_KILLED;
|
|
pCSScorer->HintMessage( "#SFUI_Notice_Hint_careful_around_teammates", false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// [tj] Added a check to make sure we don't get money for suicides.
|
|
if (pCSScorer != pCSVictim)
|
|
{
|
|
// Get the cash award from the weapon and scale it by the gamemode factor
|
|
// const CCSWeaponInfo* pWeaponInfo = CCSPlayer::GetWeaponInfoFromDamageInfo(info);
|
|
if ( pWeapon )
|
|
{
|
|
pCSScorer->AddAccountAward( PlayerCashAward::KILLED_ENEMY, pWeapon->GetKillAward(), pWeapon );
|
|
// m_DeferredCallQueue.QueueCall( pCSScorer, &CCSPlayer::AddAccountAward, PlayerCashAward::KILLED_ENEMY, award, pWeaponInfo );
|
|
}
|
|
else
|
|
{
|
|
pCSScorer->AddAccountAward( PlayerCashAward::KILLED_ENEMY );
|
|
// m_DeferredCallQueue.QueueCall( pCSScorer, &CCSPlayer::AddAccountAward, PlayerCashAward::KILLED_ENEMY );
|
|
}
|
|
}
|
|
|
|
CWeaponCSBase* pWeapon = dynamic_cast<CWeaponCSBase *>( info.GetWeapon() );
|
|
CEconItemView *pEconWeapon = pWeapon ? pWeapon->GetEconItemView() : NULL ;
|
|
|
|
EconEntity_OnOwnerKillEaterEvent( pEconWeapon, pCSScorer, pCSVictim, kKillEaterEvent_PlayerKill );
|
|
|
|
/*
|
|
if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_ENEMY_KILLED) )
|
|
{
|
|
pCSScorer->m_iDisplayHistoryBits |= DHF_ENEMY_KILLED;
|
|
pCSScorer->HintMessage( "#SFUI_Notice_Hint_win_round_by_killing_enemy", false );
|
|
}
|
|
*/
|
|
}
|
|
|
|
if ( CSGameRules()->IsPlayingCoopMission() && mp_use_respawn_waves.GetInt() == 2 )
|
|
{
|
|
if ( pCSVictim->IsBot() && pCSVictim->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
// get the number of enemies remaining and have one of the surviving CTs call it out
|
|
int count = 0;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CBaseEntity *player = UTIL_PlayerByIndex( i );
|
|
|
|
if ( player == NULL || player->GetTeamNumber() == TEAM_CT || !player->IsAlive() )
|
|
continue;
|
|
|
|
count++;
|
|
}
|
|
|
|
CCSPlayer *pCTPlayer = NULL;
|
|
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
pCTPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pCTPlayer && pCTPlayer->GetTeamNumber() == TEAM_CT )
|
|
break;
|
|
}
|
|
|
|
if ( pCTPlayer )
|
|
{
|
|
switch ( count )
|
|
{
|
|
case 3:
|
|
pCTPlayer->Radio("ThreeEnemiesLeft", "", true);
|
|
break;
|
|
case 2:
|
|
pCTPlayer->Radio("TwoEnemiesLeft", "", true);
|
|
break;
|
|
case 1:
|
|
pCTPlayer->Radio("OneEnemyLeft", "", true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::SendKickBanToGC( CCSPlayer *pPlayer, EMsgGCCStrike15_v2_MatchmakingKickBanReason_t eReason )
|
|
{
|
|
/** Removed for partner depot **/
|
|
}
|
|
|
|
void CCSGameRules::SendKickBanToGCforAccountId( uint32 uiAccountId, EMsgGCCStrike15_v2_MatchmakingKickBanReason_t eReason )
|
|
{
|
|
/** Removed for partner depot **/
|
|
}
|
|
|
|
void CCSGameRules::InitDefaultAIRelationships()
|
|
{
|
|
// Allocate memory for default relationships
|
|
CBaseCombatCharacter::AllocateDefaultRelationships();
|
|
|
|
// --------------------------------------------------------------
|
|
// First initialize table so we can report missing relationships
|
|
// --------------------------------------------------------------
|
|
int i, j;
|
|
int iNumClasses = GameRules() ? GameRules()->NumEntityClasses() : LAST_SHARED_ENTITY_CLASS;
|
|
for (i=0;i<iNumClasses;i++)
|
|
{
|
|
for (j=0;j<iNumClasses;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( ENTINDEX( 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();
|
|
}
|
|
|
|
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;
|
|
|
|
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 ); // player_falldamage
|
|
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
return fallDamage;
|
|
}
|
|
|
|
|
|
bool CCSGameRules::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen )
|
|
{
|
|
if ( !BaseClass::ClientConnected( pEntity, pszName, pszAddress, reject, maxrejectlen ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCSGameRules::ClientDisconnected( edict_t *pClient )
|
|
{
|
|
// [vitaliy] Players who disconnect while alive and reconnect in competitive mode before the end of the round will not receive win money just like suicide
|
|
if ( CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pClient ) ) )
|
|
{
|
|
if ( pPlayer->IsAlive() )
|
|
{
|
|
pPlayer->ProcessSuicideAsKillReward();
|
|
}
|
|
}
|
|
|
|
BaseClass::ClientDisconnected( pClient );
|
|
|
|
// [tj] Clear domination data when a player disconnects
|
|
if ( CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pClient ) ) )
|
|
{
|
|
pPlayer->RemoveNemesisRelationships();
|
|
}
|
|
|
|
UpdateTeamClanNames( TEAM_TERRORIST );
|
|
UpdateTeamClanNames( TEAM_CT );
|
|
|
|
if ( !IsPlayingCoopMission() )
|
|
CheckWinConditions();
|
|
}
|
|
|
|
|
|
// Called when game rules are destroyed by CWorld
|
|
void CCSGameRules::LevelShutdown()
|
|
{
|
|
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() || m_bLoadingRoundBackupData )
|
|
return false;
|
|
|
|
// If a winner has already been determined.. then get the heck out of here
|
|
if ( IsWarmupPeriod() || ( 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 );
|
|
|
|
/*********************************** GUN GAME PROGRESSIVE CHECK *******************************************************/
|
|
if ( IsPlayingGunGame() )
|
|
{
|
|
if ( GunGameProgressiveEndCheck( ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( IsPlayingGunGameProgressive() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/***************************** OTHER PLAYER's CHECK *********************************************************/
|
|
bool bNeededPlayers = false;
|
|
if ( NeededPlayersCheck( bNeededPlayers ) )
|
|
return false;
|
|
|
|
/****************************** 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;
|
|
|
|
/******************************** GUARDIAN MODE CHECK ******************************************************/
|
|
if ( IsPlayingCoopGuardian() && (
|
|
BombPlantedRoundEndCheck()
|
|
|| CTsReachedHostageRoundEndCheck()
|
|
) )
|
|
return true;
|
|
|
|
// scenario not won - still in progress
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CCSGameRules::NeededPlayersCheck( bool &bNeededPlayers )
|
|
{
|
|
if ( IsPlayingTraining() )
|
|
return false;
|
|
|
|
// When we silently are performing team manipulations then
|
|
// don't run this players check logic which might cause
|
|
// round to restart ignoring all team scores and player stats
|
|
// assignments.
|
|
if ( m_bLoadingRoundBackupData )
|
|
return false;
|
|
|
|
// Run this check differently in queue matchmaking mode
|
|
if ( IsQueuedMatchmaking() )
|
|
{
|
|
if ( !m_bFirstConnected &&
|
|
( m_iNumSpawnableTerrorist || m_iNumSpawnableCT ) )
|
|
{
|
|
m_bFreezePeriod = false; //Make sure we are not on the FreezePeriod.
|
|
m_bCompleteReset = true;
|
|
|
|
TerminateRound( 0.5f, Game_Commencing );
|
|
m_bFirstConnected = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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, "#SFUI_Notice_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( 0.5f, 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;
|
|
|
|
// Set team filters
|
|
|
|
m_filterCT.Reset();
|
|
m_filterCT.MakeReliable();
|
|
m_filterCT.AddRecipientsByTeam( GetGlobalTeam(TEAM_CT) );
|
|
|
|
m_filterTerrorist.Reset();
|
|
m_filterTerrorist.MakeReliable();
|
|
m_filterTerrorist.AddRecipientsByTeam( GetGlobalTeam(TEAM_TERRORIST) );
|
|
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::AddHostageRescueTime( void )
|
|
{
|
|
if ( m_bAnyHostageReached )
|
|
return;
|
|
|
|
m_bAnyHostageReached = true;
|
|
|
|
// If the round is already over don't add additional time
|
|
bool roundIsAlreadyOver = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE);
|
|
if ( roundIsAlreadyOver )
|
|
return;
|
|
|
|
m_iRoundTime += (int)( mp_hostages_rescuetime.GetFloat() * 60 );
|
|
|
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "#hostagerescuetime" );
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
// the number of hostages that can be left un rescued, but still win
|
|
int iNumRescuedToWin = mp_hostages_rescuetowin.GetInt() == 0 ? iNumHostages : MIN( iNumHostages, mp_hostages_rescuetowin.GetInt() );
|
|
int iNumLeftCanWin = MAX( 0, iNumHostages - iNumRescuedToWin );
|
|
|
|
m_iHostagesRemaining = iNumLeftToRescue;
|
|
|
|
if ( (iNumLeftToRescue >= iNumLeftCanWin) && (iNumHostages > 0) )
|
|
{
|
|
if ( m_iHostagesRescued >= iNumRescuedToWin )
|
|
{
|
|
if ( !bNeededPlayers )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
}
|
|
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::WIN_BY_HOSTAGE_RESCUE );
|
|
|
|
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 );
|
|
}
|
|
|
|
CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST );
|
|
if ( IsPlayingCoopMission() && pTeam )
|
|
pTeam->MarkSurrendered();
|
|
|
|
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::BombPlantedRoundEndCheck( void )
|
|
{
|
|
if ( !IsPlayingCoopGuardian() )
|
|
return false;
|
|
|
|
bool roundIsAlreadyOver = ( CSGameRules()->m_iRoundWinStatus != WINNER_NONE );
|
|
if ( roundIsAlreadyOver )
|
|
return false;
|
|
|
|
if ( m_bBombPlanted )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Planted );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCSGameRules::CTsReachedHostageRoundEndCheck( void )
|
|
{
|
|
if ( !IsPlayingCoopGuardian() )
|
|
return false;
|
|
|
|
bool roundIsAlreadyOver = ( CSGameRules()->m_iRoundWinStatus != WINNER_NONE );
|
|
if ( roundIsAlreadyOver )
|
|
return false;
|
|
|
|
if ( m_bHasHostageBeenTouched )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), CTs_ReachedHostage );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCSGameRules::GuardianAllKillsAchievedCheck( void )
|
|
{
|
|
if ( !IsPlayingCoopGuardian() )
|
|
return false;
|
|
|
|
bool roundIsAlreadyOver = ( CSGameRules()->m_iRoundWinStatus != WINNER_NONE );
|
|
if ( roundIsAlreadyOver )
|
|
return false;
|
|
|
|
if ( m_nGuardianModeSpecialKillsRemaining <= 0 )
|
|
{
|
|
if ( IsHostageRescueMap() )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_CT ) )
|
|
pTeam->MarkSurrendered();
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
}
|
|
else
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST ) )
|
|
pTeam->MarkSurrendered();
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CCSGameRules::IncrementGunGameTerroristWeapons( void )
|
|
{
|
|
if ( IsPlayingGunGameTRBomb() )
|
|
{
|
|
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;
|
|
|
|
if ( pTeam->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
// Increment the player's gun game progressive weapon
|
|
pPlayer->IncrementGunGameProgressiveWeapon( mp_ggtr_bomb_detonation_bonus.GetInt() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::IncrementGunGameCTWeapons( void )
|
|
{
|
|
if ( IsPlayingGunGameTRBomb() )
|
|
{
|
|
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;
|
|
|
|
if ( pTeam->GetTeamNumber() == TEAM_CT )
|
|
{
|
|
// Increment the player's gun game progressive weapon
|
|
pPlayer->IncrementGunGameProgressiveWeapon( mp_ggtr_bomb_defuse_bonus.GetInt() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CCSGameRules::IsBotOnlyTeam( int nTeamNumber )
|
|
{
|
|
CTeam *pTeam = GetGlobalTeam( nTeamNumber );
|
|
|
|
if ( pTeam )
|
|
{
|
|
// Loop through all players on team and determine if they are all bots
|
|
for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) );
|
|
|
|
if ( !pPlayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !pPlayer->IsBot() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCSGameRules::GunGameProgressiveEndCheck( void )
|
|
{
|
|
bool bDidEnd = false;
|
|
|
|
if ( m_match.GetPhase() == GAMEPHASE_MATCH_ENDED )
|
|
{
|
|
// No need to perform the check if the match has ended
|
|
return false;
|
|
}
|
|
|
|
if ( IsPlayingGunGameTRBomb() && !mp_ggtr_last_weapon_kill_ends_half.GetBool() )
|
|
{
|
|
// ignore kills made with the final weapon and let the round end naturally.
|
|
return false;
|
|
}
|
|
|
|
if ( !IsPlayingGunGame() )
|
|
return false;
|
|
|
|
CCSPlayer *pWinner = NULL;
|
|
|
|
// Test if a player made a kill with the final gun game weapon
|
|
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;
|
|
|
|
if ( pPlayer->MadeFinalGunGameProgressiveKill() )
|
|
{
|
|
pWinner = pPlayer;
|
|
|
|
float delayTime;
|
|
|
|
if ( IsPlayingGunGameProgressive() || IsPlayingGunGameTRBomb() )
|
|
{
|
|
delayTime = mp_ggprogressive_round_restart_delay.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
delayTime = mp_round_restart_delay.GetFloat();
|
|
}
|
|
|
|
// Award bonus points because the kill was made with the final progression weapon
|
|
if ( IsPlayingGunGameTRBomb() )
|
|
{
|
|
if ( pTeam->GetTeamNumber() == TEAM_CT )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
m_match.AddCTBonusPoints( mp_ggtr_end_round_kill_bonus.GetInt() );
|
|
}
|
|
else if ( pTeam->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
m_match.AddTerroristBonusPoints( mp_ggtr_end_round_kill_bonus.GetInt() );
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingGunGameProgressive() )
|
|
{
|
|
m_bCompleteReset = true;
|
|
|
|
bDidEnd = true;
|
|
|
|
m_match.SetPhase( GAMEPHASE_MATCH_ENDED );
|
|
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
|
|
GoToIntermission();
|
|
}
|
|
|
|
TerminateRound( delayTime, ( pTeam->GetTeamNumber() == TEAM_CT ) ? CTs_Win : Terrorists_Win );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bDidEnd )
|
|
break;
|
|
}
|
|
|
|
if ( bDidEnd && IsPlayingGunGameProgressive() )
|
|
{
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddFlag( FL_FROZEN );
|
|
pPlayer->Unblind();
|
|
}
|
|
}
|
|
}
|
|
|
|
return bDidEnd;
|
|
}
|
|
|
|
|
|
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 ) )
|
|
{
|
|
if ( !bNeededPlayers )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
}
|
|
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::TERRORIST_WIN_BOMB );
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Target_Bombed );
|
|
return true;
|
|
}
|
|
else
|
|
if ( ( m_bBombDefused == true ) && ( m_bMapHasBombTarget == true ) )
|
|
{
|
|
if ( !bNeededPlayers )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
}
|
|
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::WIN_BY_DEFUSING_BOMB );
|
|
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::PLANTED_BOMB_BUT_DEFUSED ); // give the T's a little bonus for planting the bomb even though it was defused.
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Bomb_Defused );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// [dkorus] note, this is the standard "end of round mvp" for the case where a more specific MVP condition has not been met
|
|
// examples of more specific conditions: Planting the bomb, defusing the bomb, rescuing the hostages, escaping as the VIP, etc
|
|
CCSPlayer * CCSGameRules::CalculateEndOfRoundMVP()
|
|
{
|
|
CCSPlayer* pMVP = NULL;
|
|
int maxKills = 0;
|
|
int maxDamage = 0;
|
|
CSMvpReason_t mvpReason = CSMVP_ELIMINATION;
|
|
|
|
if ( CSGameRules()->IsPlayingGunGameProgressive() )
|
|
{
|
|
// Handle winner of gun game progressive
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
if ( pPlayer->MadeFinalGunGameProgressiveKill() )
|
|
{
|
|
pMVP = pPlayer;
|
|
mvpReason = CSMVP_GUNGAMEWINNER;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int nBestScore = 0;
|
|
// Handle non-gun game progressive mvp
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
if ( pPlayer->HasBeenControlledThisRound() )
|
|
continue;
|
|
|
|
if ( CSGameRules()->IsPlayingGunGameDeathmatch() )
|
|
{
|
|
int nScore = pPlayer->GetScore();
|
|
int nEntindex = pPlayer->entindex();
|
|
if ( nScore > nBestScore || (nScore == nBestScore && nEntindex < maxKills) )
|
|
{
|
|
pMVP = pPlayer;
|
|
maxKills = nEntindex;
|
|
nBestScore = nScore;
|
|
mvpReason = CSMVP_GUNGAMEWINNER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// only consider players on the winning team
|
|
if ( CSGameRules()->m_iRoundWinStatus != WINNER_DRAW && pPlayer->GetTeamNumber() != CSGameRules()->m_iRoundWinStatus )
|
|
continue;
|
|
|
|
int nKills = pPlayer->GetNumRoundKills(); // - pPlayer->m_iNumRoundTKs; - for most eliminations count only enemies killed
|
|
int nDamage = pPlayer->GetTotalActualHealthRemovedFromEnemies();
|
|
if ( nKills > maxKills || ( nKills == maxKills && nDamage > maxDamage ) )
|
|
{
|
|
pMVP = pPlayer;
|
|
maxKills = nKills;
|
|
maxDamage = nDamage;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pMVP )
|
|
{
|
|
pMVP->IncrementNumMVPs( mvpReason );
|
|
}
|
|
|
|
return pMVP;
|
|
}
|
|
|
|
|
|
bool CCSGameRules::TeamExterminationCheck(
|
|
int NumAliveTerrorist,
|
|
int NumAliveCT,
|
|
int NumDeadTerrorist,
|
|
int NumDeadCT,
|
|
bool bNeededPlayers
|
|
)
|
|
{
|
|
bool bCTsRespawn = mp_respawn_on_death_ct.GetBool();
|
|
bool bTsRespawn = mp_respawn_on_death_t.GetBool();
|
|
|
|
if ( ( m_iNumCT > 0 && m_iNumSpawnableCT > 0 ) && ( m_iNumTerrorist > 0 && m_iNumSpawnableTerrorist > 0 ) )
|
|
{
|
|
// this checks for last man standing rules
|
|
if ( mp_teammates_are_enemies.GetBool() )
|
|
{
|
|
// last CT alive
|
|
if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && !bTsRespawn && NumAliveCT == 1 )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win );
|
|
return true;
|
|
}
|
|
|
|
if ( NumAliveCT == 0 && NumDeadCT != 0 && !bCTsRespawn && NumAliveTerrorist == 1 )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
return true;
|
|
}
|
|
|
|
if ( NumAliveCT == 0 && !bCTsRespawn && NumAliveTerrorist == 0 && !bTsRespawn && ( m_iNumTerrorist > 0 || m_iNumCT > 0 ) )
|
|
{
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw );
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// CTs WON (if they don't respawn)
|
|
if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && !bTsRespawn && 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 ( !bNeededPlayers )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
}
|
|
|
|
if ( m_bMapHasBombTarget )
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::ELIMINATION_BOMB_MAP );
|
|
else
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::ELIMINATION_HOSTAGE_MAP_CT );
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win );
|
|
return true;
|
|
}
|
|
}
|
|
else if ( mp_use_respawn_waves.GetInt() == 2 && (IsPlayingCoopGuardian() || IsPlayingCoopMission()) )
|
|
{
|
|
int nTeam = IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
|
|
int nOtherTeam = IsHostageRescueMap() ? TEAM_CT : TEAM_TERRORIST;
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
nTeam = TEAM_CT;
|
|
nOtherTeam = TEAM_TERRORIST;
|
|
}
|
|
|
|
if ( (IsHostageRescueMap() && NumAliveCT == 0 && NumDeadCT != 0 && bCTsRespawn) || ((IsBombDefuseMap() || IsPlayingCoopMission()) && NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && bTsRespawn) )
|
|
{
|
|
// HACK
|
|
//play a sound here for all players on other team
|
|
for ( int j = 1; j <= MAX_PLAYERS; j++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( j ) );
|
|
if ( pPlayer && pPlayer->GetTeamNumber() == nTeam )
|
|
{
|
|
CSingleUserRecipientFilter filter( pPlayer );
|
|
pPlayer->EmitSound( filter, pPlayer->entindex(), "UI.ArmsRace.BecomeMatchLeader" );
|
|
|
|
ConVarRef mp_buy_anywhere( "mp_buy_anywhere" );
|
|
if ( IsPlayingCoopGuardian() && pPlayer->IsAlive() && mp_buy_anywhere.GetBool() )
|
|
{
|
|
// force the player to get more ammo
|
|
pPlayer->GuardianForceFillAmmo();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
{
|
|
// if T's are supposed to respawn
|
|
float flNextRespawn = gpGlobals->curtime + GetRespawnWaveMaxLength( nOtherTeam );
|
|
m_flNextRespawnWave.Set( nOtherTeam, flNextRespawn );
|
|
|
|
m_flCoopRespawnAndHealTime = gpGlobals->curtime + MIN( 2.0f, GetRespawnWaveMaxLength( nOtherTeam ) );
|
|
|
|
// decide if the players can buy now
|
|
int iBuyStatus = sv_buy_status_override.GetInt();
|
|
if ( iBuyStatus > 0 && ((nTeam == TEAM_CT && iBuyStatus != 1) || (nTeam == TEAM_TERRORIST && iBuyStatus != 2)) )
|
|
{
|
|
m_flGuardianBuyUntilTime = -1;
|
|
}
|
|
else
|
|
{
|
|
m_flGuardianBuyUntilTime = flNextRespawn + 7.0f;
|
|
}
|
|
|
|
char szSec[32];
|
|
Q_snprintf(szSec, sizeof(szSec), "%d", (int)GetRespawnWaveMaxLength( nOtherTeam ));
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
UTIL_ClientPrintFilter(filter, HUD_PRINTCENTER, "#SFUI_Notice_NextWaveIn", szSec);
|
|
|
|
// give CTs a bonus for surving the round
|
|
AddTeamAccount( nTeam, TeamCashAward::SURVIVE_GUARDIAN_WAVE );
|
|
}
|
|
|
|
// COOP MISSION
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
// output to the game entity here that says, we completed the room
|
|
// and open the door to the next room
|
|
CGameCoopMissionManager *pManager = GetCoopMissionManager();
|
|
if ( !pManager )
|
|
{
|
|
DevMsg( "Coop mission map is missing a game_coopmission_manager entity. You can't keep track of completed waves without it!" );
|
|
}
|
|
else
|
|
{
|
|
pManager->SetWaveCompleted();
|
|
m_flCoopRespawnAndHealTime = gpGlobals->curtime + 3.0f;
|
|
}
|
|
}
|
|
|
|
// give Ts something so they aren't always using pistols
|
|
//AddTeamAccount( nOtherTeam, TeamCashAward::CUSTOM_AWARD, 1800, "" );
|
|
|
|
// Super-specific hack for coop. After CTs clear a round, the easiest bot on that team
|
|
// gets one level harder.
|
|
CTeam *pTeam = GetGlobalTeam( nOtherTeam );
|
|
if ( pTeam && sv_bots_get_harder_after_each_wave.GetInt() > 0 )
|
|
{
|
|
// increase difficulty of 2 bots
|
|
for ( int i=0; i < sv_bots_get_harder_after_each_wave.GetInt(); i++ )
|
|
{
|
|
CUtlVector < CCSBot* > vecBotsOnTeam;
|
|
if ( pTeam->GetBotMembers( &vecBotsOnTeam ) )
|
|
{
|
|
CCSBot *pLeadBot = NULL;
|
|
BotDifficultyType botHighDifficulty = BOT_EXPERT;
|
|
FOR_EACH_VEC( vecBotsOnTeam, i )
|
|
{
|
|
const BotProfile *pProfile = vecBotsOnTeam[i]->GetProfile();
|
|
if ( pProfile->GetMaxDifficulty() < botHighDifficulty )
|
|
{
|
|
pLeadBot = vecBotsOnTeam[i];
|
|
botHighDifficulty = ( BotDifficultyType )pProfile->GetMaxDifficulty();
|
|
}
|
|
}
|
|
if ( pLeadBot )
|
|
{
|
|
const BotProfile* pNewProfileData = TheBotProfiles->GetRandomProfile( MIN( ( BotDifficultyType )( botHighDifficulty + 1 ), BOT_EXPERT ), pLeadBot->GetTeamNumber(), WEAPONTYPE_UNKNOWN, true );
|
|
if ( pNewProfileData )
|
|
{
|
|
pLeadBot->Initialize( pNewProfileData, pLeadBot->GetTeamNumber() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now just randomize all of the profiles
|
|
CUtlVector < CCSBot* > vecBotsOnTeam;
|
|
if ( pTeam->GetBotMembers( &vecBotsOnTeam ) )
|
|
{
|
|
FOR_EACH_VEC( vecBotsOnTeam, i )
|
|
{
|
|
const BotProfile *pProfile = vecBotsOnTeam[i]->GetProfile();
|
|
const BotProfile* pNewProfileData = TheBotProfiles->GetRandomProfile( ( BotDifficultyType )( pProfile->GetMaxDifficulty() ), vecBotsOnTeam[i]->GetTeamNumber(), WEAPONTYPE_UNKNOWN, true );
|
|
if ( pNewProfileData )
|
|
{
|
|
vecBotsOnTeam[i]->Initialize( pNewProfileData, vecBotsOnTeam[i]->GetTeamNumber() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// // let players buy
|
|
// int iBuyStatus = -1;
|
|
// if ( sv_buy_status_override.GetInt() >= 0 )
|
|
// {
|
|
// iBuyStatus = sv_buy_status_override.GetInt();
|
|
// }
|
|
// else if ( g_pMapInfo )
|
|
// {
|
|
// // Check to see if there's a mapping info parameter entity
|
|
// iBuyStatus = g_pMapInfo->m_iBuyingStatus;
|
|
// }
|
|
//
|
|
// if ( iBuyStatus >= 0 )
|
|
// {
|
|
// switch ( iBuyStatus )
|
|
// {
|
|
// 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;
|
|
// }
|
|
// }
|
|
//
|
|
// // if ( nTeam == TEAM_CT )
|
|
// // m_bCTCantBuy = false;
|
|
// // else
|
|
// // m_bTCantBuy = false;
|
|
}
|
|
}
|
|
|
|
// Terrorists WON (if they don't respawn)
|
|
if ( NumAliveCT == 0 && NumDeadCT != 0 && !bCTsRespawn && m_iNumSpawnableTerrorist > 0 )
|
|
{
|
|
if ( !bNeededPlayers )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
}
|
|
|
|
if ( m_bMapHasBombTarget )
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::ELIMINATION_BOMB_MAP );
|
|
else
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::ELIMINATION_HOSTAGE_MAP_T );
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
return true;
|
|
}
|
|
else if ( mp_use_respawn_waves.GetInt() == 2 && NumAliveCT == 0 && NumDeadCT != 0 && bCTsRespawn )
|
|
{
|
|
// if T's are supposed to respawn
|
|
m_flNextRespawnWave.Set( TEAM_CT, gpGlobals->curtime + GetRespawnWaveMaxLength( TEAM_CT ) );
|
|
}
|
|
}
|
|
}
|
|
else if ( NumAliveCT == 0 && !bCTsRespawn && NumAliveTerrorist == 0 && !bTsRespawn && ( m_iNumTerrorist > 0 || m_iNumCT > 0 ) )
|
|
{
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CCSGameRules::ReadMultiplayCvars()
|
|
{
|
|
float flRoundTime = 0;
|
|
|
|
if ( IsPlayingClassic() && IsHostageRescueMap() && ( mp_roundtime_hostage.GetFloat() > 0 ) )
|
|
{
|
|
flRoundTime = mp_roundtime_hostage.GetFloat();
|
|
}
|
|
else if ( IsPlayingClassic() && IsBombDefuseMap() && ( mp_roundtime_defuse.GetFloat() > 0 ) )
|
|
{
|
|
flRoundTime = mp_roundtime_defuse.GetFloat();
|
|
}
|
|
else if ( IsPlayingCoopMission() )
|
|
{
|
|
flRoundTime = mp_roundtime_deployment.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
flRoundTime = mp_roundtime.GetFloat();
|
|
}
|
|
|
|
m_iRoundTime = IsWarmupPeriod() ? 999 : (int)( flRoundTime * 60 );
|
|
m_iFreezeTime = IsWarmupPeriod() ? 2 : mp_freezetime.GetInt();
|
|
}
|
|
|
|
static int BombSortPredicate(CCSPlayer * const *left, CCSPlayer * const *right)
|
|
{
|
|
// should we prioritize humans over bots?
|
|
if (cv_bot_defer_to_human_items.GetBool() )
|
|
{
|
|
if ( (*left)->IsBot() && !(*right)->IsBot() )
|
|
return 1;
|
|
|
|
if ( !(*left)->IsBot() && (*right)->IsBot() )
|
|
return -1;
|
|
}
|
|
|
|
if ( (*left)->m_fLastGivenBombTime < (*right)->m_fLastGivenBombTime )
|
|
return -1;
|
|
|
|
if ( (*left)->m_fLastGivenBombTime > (*right)->m_fLastGivenBombTime )
|
|
return +1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int SpawnPointSortFunction( SpawnPoint* const *left, SpawnPoint* const *right )
|
|
{
|
|
// Sort 2 spawn points against each other using their priority values
|
|
return ( *left )->m_iPriority - ( *right )->m_iPriority;
|
|
}
|
|
|
|
static int CoopEnemySpawnSortFunction( SpawnPoint* const *left, SpawnPoint* const *right )
|
|
{
|
|
if ( !CSGameRules() )
|
|
return 0;
|
|
|
|
// Sort the spawn point lists
|
|
// just use the first overlapping nav area as a reasonable approximation
|
|
ShortestPathCost cost = ShortestPathCost();
|
|
float dist_left = NavAreaTravelDistance( TheNavMesh->GetNearestNavArea( ( *left )->GetAbsOrigin() ),
|
|
TheNavMesh->GetNearestNavArea( CSGameRules()->m_vecMainCTSpawnPos ),
|
|
cost );
|
|
|
|
float dist_right = NavAreaTravelDistance( TheNavMesh->GetNearestNavArea( ( *right )->GetAbsOrigin() ),
|
|
TheNavMesh->GetNearestNavArea( CSGameRules()->m_vecMainCTSpawnPos ),
|
|
cost );
|
|
|
|
return dist_left - dist_right;
|
|
}
|
|
|
|
static int ArmsRaceSpawnPointSortFunction( SpawnPoint* const *left, SpawnPoint* const *right )
|
|
{
|
|
if ( ( *left )->m_nType != SpawnPoint::ArmsRace && ( *right )->m_nType == SpawnPoint::ArmsRace )
|
|
return 1;
|
|
|
|
if ( ( *left )->m_nType == SpawnPoint::ArmsRace && ( *right )->m_nType != SpawnPoint::ArmsRace )
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CCSGameRules::ResetMasterSpawnPointsForCoop( void )
|
|
{
|
|
RefreshCurrentSpawnPointLists();
|
|
}
|
|
|
|
// Perform round-related processing at the point when there is less than 1 second of "free play" to go before the round officially ends
|
|
// At this point the round winner has been determined and displayed to the players
|
|
void CCSGameRules::PreRestartRound( void )
|
|
{
|
|
IGameEvent *restartEvent = gameeventmanager->CreateEvent( "cs_pre_restart" );
|
|
gameeventmanager->FireEvent( restartEvent );
|
|
m_bHasTriggeredRoundStartMusic = true;
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
// make sure all active spawn points are enabled again
|
|
ResetMasterSpawnPointsForCoop();
|
|
}
|
|
else
|
|
{
|
|
// reshuffle spawns and then sort by priority for the next round.
|
|
ShuffleSpawnPointLists();
|
|
SortSpawnPointLists();
|
|
}
|
|
|
|
// TEMP
|
|
// for ( int i=0; i < WEAPON_LAST; i++ )
|
|
// {
|
|
// const CCSWeaponInfo *pCSWeaponInfo = GetWeaponInfo( (CSWeaponID)i );
|
|
// if ( pCSWeaponInfo )
|
|
// Msg( "%s is worth %d points.\n", pCSWeaponInfo->szPrintName, GetWeaponScoreForDeathmatch( (CSWeaponID)i ) );
|
|
// }
|
|
}
|
|
|
|
// Perform round-related processing at the point where the round winner has been determined, and free-play has begun
|
|
void CCSGameRules::RoundWin( void )
|
|
{
|
|
// 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 += TeamCashAwardValue( TeamCashAward::HOSTAGE_ALIVE );
|
|
}
|
|
|
|
if ( iRescuedHostageBonus >= 2000 )
|
|
break;
|
|
}
|
|
|
|
|
|
// Super-specific hack for coop. After a team wins, the hardest bot on that team
|
|
// gets easier.
|
|
if ( m_iRoundWinStatus == TEAM_TERRORIST || m_iRoundWinStatus == TEAM_CT )
|
|
{
|
|
CTeam *pTeam = GetGlobalTeam( m_iRoundWinStatus );
|
|
if ( pTeam && IsPlayingCoopGuardian() )
|
|
{
|
|
for ( int j = 0; j < m_nGuardianModeWaveNumber; j++ )
|
|
{
|
|
for ( int i = 0; i < sv_bots_get_easier_each_win.GetInt(); i++ )
|
|
{
|
|
CUtlVector < CCSBot* > vecBotsOnTeam;
|
|
if ( pTeam->GetBotMembers( &vecBotsOnTeam ) )
|
|
{
|
|
CCSBot *pLeadBot = NULL;
|
|
BotDifficultyType botHighDifficulty = BOT_EASY;
|
|
FOR_EACH_VEC( vecBotsOnTeam, i )
|
|
{
|
|
const BotProfile *pProfile = vecBotsOnTeam[i]->GetProfile();
|
|
if ( pProfile->GetMaxDifficulty() > botHighDifficulty )
|
|
{
|
|
pLeadBot = vecBotsOnTeam[i];
|
|
botHighDifficulty = ( BotDifficultyType )pProfile->GetMaxDifficulty();
|
|
}
|
|
}
|
|
if ( pLeadBot )
|
|
{
|
|
// dont go lower than the map set bot difficulty
|
|
ConVarRef bot_difficulty( "bot_difficulty" );
|
|
const BotProfile* pNewProfileData = TheBotProfiles->GetRandomProfile( MAX( ( BotDifficultyType )( botHighDifficulty - 1 ), ( BotDifficultyType )bot_difficulty.GetInt() ), pLeadBot->GetTeamNumber(), WEAPONTYPE_UNKNOWN, true );
|
|
if ( pNewProfileData )
|
|
{
|
|
pLeadBot->Initialize( pNewProfileData, pLeadBot->GetTeamNumber() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int nRoundsPlayed = GetTotalRoundsPlayed();
|
|
if ( nRoundsPlayed > 3 )
|
|
{
|
|
int nShouldReport = nRoundsPlayed % 2;
|
|
|
|
if ( nShouldReport == 0 )
|
|
{
|
|
int nHumanGuardianTeam = IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
|
|
if ( m_iRoundWinStatus != nHumanGuardianTeam )
|
|
{ // Only print the message if the humans didn't complete the mission yet
|
|
CBroadcastRecipientFilter filter;
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_GuardianModeLowerDifficultyNextRound" );
|
|
}
|
|
|
|
ConVarRef sv_bots_get_harder_after_each_wave( "sv_bots_get_harder_after_each_wave" );
|
|
sv_bots_get_harder_after_each_wave.SetValue( MAX( 1, sv_bots_get_harder_after_each_wave.GetInt()-1) );
|
|
|
|
ConVarRef bot_difficulty( "bot_difficulty" );
|
|
int nBotDifficulty = bot_difficulty.GetInt();
|
|
if ( nRoundsPlayed > 9 )
|
|
nBotDifficulty = 0;
|
|
else if ( nRoundsPlayed > 5 )
|
|
nBotDifficulty = MIN( nBotDifficulty, 1 );
|
|
else if ( nRoundsPlayed > 3 )
|
|
nBotDifficulty = MIN( nBotDifficulty, 2 );
|
|
|
|
bot_difficulty.SetValue( MAX( 0, sv_bots_get_harder_after_each_wave.GetInt()-1) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//*******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 > 0 )
|
|
{
|
|
// reset the loser bonus
|
|
m_iLoserBonus = TeamCashAwardValue( TeamCashAward::LOSER_BONUS );
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
}
|
|
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 > 0 )
|
|
{
|
|
// reset the loser bonus
|
|
m_iLoserBonus = TeamCashAwardValue( TeamCashAward::LOSER_BONUS );
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
}
|
|
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 hasn't maxed out.
|
|
if((m_iNumConsecutiveTerroristLoses > 1) && (m_iLoserBonus < 3000))
|
|
m_iLoserBonus += TeamCashAwardValue( TeamCashAward::LOSER_BONUS_CONSECUTIVE_ROUNDS );//help out the team in the losing streak
|
|
else
|
|
if((m_iNumConsecutiveCTLoses > 1) && (m_iLoserBonus < 3000))
|
|
m_iLoserBonus += TeamCashAwardValue( TeamCashAward::LOSER_BONUS_CONSECUTIVE_ROUNDS );//help out the team in the losing streak
|
|
|
|
// assign the wining and losing bonuses
|
|
if (m_iRoundWinStatus == WINNER_TER) // terrorists won
|
|
{
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::HOSTAGE_ALIVE, iRescuedHostageBonus );
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::LOSER_BONUS, m_iLoserBonus );
|
|
}
|
|
else if (m_iRoundWinStatus == WINNER_CT) // CT Won
|
|
{
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::HOSTAGE_ALIVE, iRescuedHostageBonus);
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::LOSER_BONUS, m_iLoserBonus );
|
|
}
|
|
|
|
//Update CT account based on number of hostages rescued
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::RESCUED_HOSTAGE, m_iHostagesRescued * TeamCashAwardValue( TeamCashAward::RESCUED_HOSTAGE ));
|
|
|
|
// store win reason in match stats
|
|
|
|
if ( ShouldRecordMatchStats() )
|
|
{
|
|
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
|
|
InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
|
|
|
|
bool bCTsRespawn = mp_respawn_on_death_ct.GetBool();
|
|
bool bTsRespawn = mp_respawn_on_death_t.GetBool();
|
|
|
|
int iNumRescuedToWin = mp_hostages_rescuetowin.GetInt() == 0 ? g_Hostages.Count() : MIN( g_Hostages.Count(), mp_hostages_rescuetowin.GetInt() );
|
|
|
|
int nStatToModify = GetTotalRoundsPlayed() - 1;
|
|
|
|
if ( nStatToModify >= 0 )
|
|
{
|
|
nStatToModify = MIN( nStatToModify, m_iMatchStats_RoundResults.Count() - 1 );
|
|
|
|
m_iMatchStats_PlayersAlive_T.GetForModify( nStatToModify ) = NumAliveTerrorist;
|
|
DevMsg( "NumAliveTerrorist = %d", NumAliveTerrorist );
|
|
|
|
m_iMatchStats_PlayersAlive_CT.GetForModify( nStatToModify ) = NumAliveCT;
|
|
DevMsg( "NumAliveCT = %d", NumAliveCT );
|
|
|
|
if (m_iRoundWinStatus == WINNER_TER) // terrorists won
|
|
{
|
|
// T_WIN_BOMB
|
|
if ( m_bTargetBombed == true )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::T_WIN_BOMB;
|
|
DevMsg( "Ts won by BOMB\n" );
|
|
}
|
|
// T_WIN_ELIMINATION
|
|
else if ( NumAliveCT == 0 && NumDeadCT != 0 && !bCTsRespawn && m_iNumSpawnableTerrorist > 0 )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::T_WIN_ELIMINATION;
|
|
DevMsg( "Ts won by ELIMINATION\n" );
|
|
}
|
|
// T_WIN_TIME
|
|
else if ( GetRoundRemainingTime() <= 0 )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::T_WIN_TIME;
|
|
DevMsg( "Ts won by TIME\n" );
|
|
}
|
|
else
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::UNKNOWN;
|
|
DevMsg( "Ts won, but the reason is UNKNOWN!!!\n" );
|
|
}
|
|
}
|
|
else if (m_iRoundWinStatus == WINNER_CT) // cts won
|
|
{
|
|
// CT_WIN_DEFUSE
|
|
if ( m_bBombDefused == true )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::CT_WIN_DEFUSE;
|
|
DevMsg( "CTs won by DEFUSE\n" );
|
|
}
|
|
// CT_WIN_ELIMINATION
|
|
else if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && !bTsRespawn && m_iNumSpawnableCT > 0 )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::CT_WIN_ELIMINATION;
|
|
DevMsg( "CTs won by ELIMINATION\n" );
|
|
}
|
|
// CT_WIN_TIME
|
|
else if ( GetRoundRemainingTime() <= 0 )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::CT_WIN_TIME;
|
|
DevMsg( "CTs won by TIME\n" );
|
|
}
|
|
// CT_WIN_RESCUE
|
|
else if ( iNumRescuedToWin > 0 && m_iHostagesRescued >= iNumRescuedToWin )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::CT_WIN_RESCUE;
|
|
DevMsg( "CTs won by RESCUE\n" );
|
|
}
|
|
else
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::UNKNOWN;
|
|
DevMsg( "CTs won, but the reason is UNKNOWN!!!\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( nStatToModify ) = RoundResult::UNKNOWN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform round-related processing at the official end of round
|
|
void CCSGameRules::RoundEnd( void )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_officially_ended" );
|
|
if( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
if ( m_bPlayerItemsHaveBeenDisplayed )
|
|
ClearItemsDroppedDuringMatch();
|
|
}
|
|
|
|
// Perform round-related processing at the point when the next round is beginning
|
|
void CCSGameRules::RestartRound()
|
|
{
|
|
// save off how many players were alive at the very end of this round
|
|
if ( ShouldRecordMatchStats() )
|
|
{
|
|
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
|
|
InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
|
|
|
|
int nStatToModify = GetTotalRoundsPlayed() - 1;
|
|
|
|
if ( nStatToModify >= 0 )
|
|
{
|
|
nStatToModify = MIN( nStatToModify, m_iMatchStats_PlayersAlive_T.Count() - 1 );
|
|
|
|
m_iMatchStats_PlayersAlive_T.GetForModify( nStatToModify ) = NumAliveTerrorist;
|
|
DevMsg( "NumAliveTerrorist = %d", NumAliveTerrorist );
|
|
|
|
m_iMatchStats_PlayersAlive_CT.GetForModify( nStatToModify ) = NumAliveCT;
|
|
DevMsg( "NumAliveCT = %d", NumAliveCT );
|
|
}
|
|
}
|
|
|
|
m_iNextCTSpawnPoint = 0;
|
|
m_iNextTerroristSpawnPoint = 0;
|
|
|
|
// fire a round_prestart event
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_prestart" );
|
|
if( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
// FIXME[pmf]: move this up to the pre-restart?
|
|
// [tj] Notify players that the round is about to be reset
|
|
int playerCount = 0;
|
|
for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( clientIndex );
|
|
if ( pPlayer )
|
|
{
|
|
playerCount++;
|
|
pPlayer->OnPreResetRound();
|
|
|
|
}
|
|
}
|
|
|
|
GetGlobalTeam( TEAM_CT )->ResetTeamLeaders();
|
|
GetGlobalTeam( TEAM_TERRORIST )->ResetTeamLeaders();
|
|
|
|
// If the pre-reset round was causing team changes then make sure we stop silencing them from now on
|
|
m_bForceTeamChangeSilent = false;
|
|
|
|
// Also after the round restarts we mark ourselves as no longer loading round backup data
|
|
m_bLoadingRoundBackupData = false;
|
|
|
|
// Kick any bots flagged for removal
|
|
for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( clientIndex );
|
|
if ( pPlayer && pPlayer->IsBot() && ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR || pPlayer->GetPendingTeamNumber() == TEAM_SPECTATOR ) )
|
|
{
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", engine->GetPlayerUserId( pPlayer->edict() ) ) );
|
|
}
|
|
}
|
|
|
|
// Perform any queued team moves
|
|
for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( clientIndex );
|
|
if ( pPlayer && ( pPlayer->GetPendingTeamNumber() != pPlayer->GetTeamNumber() ) )
|
|
{
|
|
pPlayer->HandleCommand_JoinTeam( pPlayer->GetPendingTeamNumber() ) ;
|
|
}
|
|
}
|
|
|
|
// Team pre-round setup. Do this after players have finished switching teams.
|
|
GetGlobalCSTeam( TEAM_CT )->OnRoundPreStart();
|
|
GetGlobalCSTeam( TEAM_TERRORIST )->OnRoundPreStart();
|
|
|
|
if ( m_endMatchOnRoundReset )
|
|
{
|
|
m_endMatchOnRoundReset = false;
|
|
m_endMatchOnThink = true;
|
|
}
|
|
|
|
#if defined ( _GAMECONSOLE )
|
|
bool isReallyEndOfRound = false;
|
|
if ((m_iRoundWinStatus == WINNER_TER) || (m_iRoundWinStatus == WINNER_CT))
|
|
isReallyEndOfRound = true;
|
|
|
|
if (playerCount > 1 && isReallyEndOfRound)
|
|
{
|
|
IGameEvent * updateMatchStatsEvent = gameeventmanager->CreateEvent( "update_matchmaking_stats" );
|
|
if (updateMatchStatsEvent)
|
|
{
|
|
gameeventmanager->FireEvent( updateMatchStatsEvent);
|
|
}
|
|
|
|
IGameEvent * writeProfileEvent = gameeventmanager->CreateEvent( "write_profile_data" );
|
|
if ( writeProfileEvent )
|
|
{
|
|
gameeventmanager->FireEvent( writeProfileEvent );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( !IsFinite( gpGlobals->curtime ) )
|
|
{
|
|
Warning( "NaN curtime in RestartRound\n" );
|
|
gpGlobals->curtime = 0.0f;
|
|
}
|
|
|
|
|
|
// Brock H. - TR - 03/31/09
|
|
// Revert any player controlled bots
|
|
#if CS_CONTROLLABLE_BOTS_ENABLED
|
|
RevertBotsFunctor revertBots;
|
|
ForEachPlayer( revertBots );
|
|
#endif
|
|
|
|
m_iTotalRoundsPlayed++;
|
|
|
|
//ClearBodyQue();
|
|
|
|
// Tabulate the number of players on each team.
|
|
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
|
|
InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
|
|
|
|
m_bBombDropped = false;
|
|
m_bBombPlanted = false;
|
|
m_bHasHostageBeenTouched = false;
|
|
|
|
if ( GetHumanTeam() != TEAM_UNASSIGNED )
|
|
{
|
|
MoveHumansToHumanTeam();
|
|
}
|
|
|
|
|
|
#if !defined ( _GAMECONSOLE )
|
|
ProcessAutoBalance();
|
|
#endif
|
|
|
|
//If this is the first restart since halftime, do the appropriate bookkeeping.
|
|
bool bClearAccountsAfterHalftime = false;
|
|
if ( IsPlayingGunGameProgressive() )
|
|
{
|
|
ClearGunGameData();
|
|
}
|
|
else if ( m_match.GetPhase() == GAMEPHASE_HALFTIME )
|
|
{
|
|
if ( GetOvertimePlaying() && ( m_match.GetRoundsPlayed() <= ( mp_maxrounds.GetInt() + ( GetOvertimePlaying() - 1 )*mp_overtime_maxrounds.GetInt() ) ) )
|
|
{
|
|
// This is the overtime halftime at the end of a tied regulation time or at the end of a previous overtime that
|
|
// failed to determine the winner, we will not be switching teams at this time and we proceed into first half
|
|
// of next overtime period
|
|
m_match.SetPhase( GAMEPHASE_PLAYING_FIRST_HALF );
|
|
}
|
|
else
|
|
{
|
|
// Regulation halftime or 1st half of overtime finished, swap the CT and T scores so the scoreboard will be correct
|
|
m_match.SwapTeamScores();
|
|
m_match.SetPhase( GAMEPHASE_PLAYING_SECOND_HALF );
|
|
}
|
|
|
|
if ( IsPlayingGunGameTRBomb() )
|
|
{
|
|
ClearGunGameData();
|
|
}
|
|
else
|
|
{
|
|
// Ensure everyone is given only the starting money
|
|
bClearAccountsAfterHalftime = true;
|
|
}
|
|
|
|
// Remove all items at halftime or before overtime when teams aren't switching sides
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
pPlayer->RemoveAllItems( true );
|
|
}
|
|
}
|
|
// Check to see if TR Gun Game match has ended, and player data should be reset
|
|
else if ( m_match.GetPhase() == GAMEPHASE_MATCH_ENDED )
|
|
{
|
|
if ( IsPlayingGunGameTRBomb() )
|
|
{
|
|
ClearGunGameData();
|
|
}
|
|
}
|
|
|
|
if ( m_bCompleteReset )
|
|
{
|
|
// reset timeouts
|
|
EndTerroristTimeOut();
|
|
EndCTTimeOut();
|
|
|
|
m_nTerroristTimeOuts = mp_team_timeout_max.GetInt();
|
|
m_nCTTimeOuts = mp_team_timeout_max.GetInt();
|
|
|
|
m_flTerroristTimeOutRemaining = mp_team_timeout_time.GetInt();
|
|
m_flCTTimeOutRemaining = mp_team_timeout_time.GetInt();
|
|
|
|
// bounds check
|
|
if ( mp_timelimit.GetInt() < 0 )
|
|
{
|
|
mp_timelimit.SetValue( 0 );
|
|
}
|
|
|
|
if ( m_bScrambleTeamsOnRestart )
|
|
{
|
|
HandleScrambleTeams();
|
|
m_bScrambleTeamsOnRestart = false;
|
|
|
|
if ( IsPlayingGunGameTRBomb() )
|
|
{
|
|
ClearGunGameData();
|
|
}
|
|
}
|
|
|
|
if ( m_bSwapTeamsOnRestart )
|
|
{
|
|
HandleSwapTeams();
|
|
m_bSwapTeamsOnRestart = false;
|
|
}
|
|
|
|
|
|
m_flGameStartTime = gpGlobals->curtime;
|
|
if ( !IsFinite( m_flGameStartTime.Get() ) )
|
|
{
|
|
Warning( "Trying to set a NaN game start time\n" );
|
|
m_flGameStartTime.GetForModify() = 0.0f;
|
|
}
|
|
m_iTotalRoundsPlayed = 0;
|
|
|
|
// Reset score info
|
|
m_match.Reset();
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
|
|
m_iAccountTerrorist = m_iAccountCT = 0; //No extra cash!.
|
|
|
|
//We are starting fresh. So it's like no one has ever won or lost.
|
|
m_iLoserBonus = TeamCashAwardValue( TeamCashAward::LOSER_BONUS );
|
|
|
|
}
|
|
|
|
if ( m_bCompleteReset || // player scores and QMM fully reset when players lost coop mission round!
|
|
IsPlayingCoopMission() )
|
|
{
|
|
// Reset the player stats
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = CCSPlayer::Instance( i );
|
|
|
|
if ( pPlayer && !FNullEnt( pPlayer->edict() ) )
|
|
pPlayer->Reset( true );
|
|
}
|
|
|
|
// For queued matchmaking mode reset KDA/MVPs as well and initialize money for start
|
|
if ( IsQueuedMatchmaking() )
|
|
{
|
|
FOR_EACH_MAP( m_mapQueuedMatchmakingPlayersData, idxQueuedPlayer )
|
|
{
|
|
m_mapQueuedMatchmakingPlayersData.Element( idxQueuedPlayer )->Reset();
|
|
m_mapQueuedMatchmakingPlayersData.Element( idxQueuedPlayer )->m_cash = GetStartMoney();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_mapQueuedMatchmakingPlayersData.PurgeAndDeleteElements();
|
|
}
|
|
|
|
// Save a zeroeth round backup
|
|
if ( Helper_mp_backup_round_IsEnabled() && !IsWarmupPeriod() )
|
|
{
|
|
SaveRoundDataInformation();
|
|
}
|
|
}
|
|
|
|
m_bFreezePeriod = true;
|
|
m_bGameRestart = false;
|
|
|
|
UTIL_LogPrintf( "Starting Freeze period\n" );
|
|
|
|
ReadMultiplayCvars();
|
|
|
|
int iBuyStatus = -1;
|
|
if ( sv_buy_status_override.GetInt() >= 0 )
|
|
{
|
|
iBuyStatus = sv_buy_status_override.GetInt();
|
|
}
|
|
else if ( g_pMapInfo )
|
|
{
|
|
// Check to see if there's a mapping info parameter entity
|
|
iBuyStatus = g_pMapInfo->m_iBuyingStatus;
|
|
}
|
|
|
|
if ( iBuyStatus >= 0 )
|
|
{
|
|
switch ( iBuyStatus )
|
|
{
|
|
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" ) )
|
|
{
|
|
// this is a bit hacky, but it makes it so the bomb stuff only shows up on mission 3 of the coop mission
|
|
if ( IsPlayingCoopMission() && mp_anyone_can_pickup_c4.GetBool() == false )
|
|
{
|
|
m_bMapHasBombTarget = false;
|
|
m_bMapHasBombZone = false;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
|
|
/*
|
|
// 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 += TeamCashAwardValue( TeamCashAward::HOSTAGE_ALIVE );
|
|
}
|
|
|
|
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 > 0 )
|
|
{
|
|
// reset the loser bonus
|
|
m_iLoserBonus = TeamCashAwardValue( TeamCashAward::LOSER_BONUS );
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
}
|
|
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 > 0 )
|
|
{
|
|
// reset the loser bonus
|
|
m_iLoserBonus = TeamCashAwardValue( TeamCashAward::LOSER_BONUS );
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
}
|
|
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 hasn't maxed out.
|
|
if((m_iNumConsecutiveTerroristLoses > 1) && (m_iLoserBonus < 3000))
|
|
m_iLoserBonus += TeamCashAwardValue( TeamCashAward::LOSER_BONUS_CONSECUTIVE_ROUNDS );//help out the team in the losing streak
|
|
else
|
|
if((m_iNumConsecutiveCTLoses > 1) && (m_iLoserBonus < 3000))
|
|
m_iLoserBonus += TeamCashAwardValue( TeamCashAward::LOSER_BONUS_CONSECUTIVE_ROUNDS );//help out the team in the losing streak
|
|
|
|
// assign the wining and losing bonuses
|
|
if (m_iRoundWinStatus == WINNER_TER) // terrorists won
|
|
{
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::HOSTAGE_ALIVE, iRescuedHostageBonus );
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::LOSER_BONUS, m_iLoserBonus );
|
|
}
|
|
else if (m_iRoundWinStatus == WINNER_CT) // CT Won
|
|
{
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::HOSTAGE_ALIVE, iRescuedHostageBonus);
|
|
if (m_bMapHasEscapeZone == false) // only give them the bonus if this isn't an escape map
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::LOSER_BONUS, m_iLoserBonus);
|
|
}
|
|
|
|
|
|
//Update CT account based on number of hostages rescued
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::RESCUED_HOSTAGE, m_iHostagesRescued * TeamCashAwardValue( TeamCashAward::RESCUED_HOSTAGE ));
|
|
*/
|
|
|
|
// 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;
|
|
}
|
|
|
|
m_bRoundTimeWarningTriggered = false;
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
{
|
|
if ( CanSpendMoneyInMap() )
|
|
m_flGuardianBuyUntilTime = GetRoundStartTime();
|
|
}
|
|
|
|
//Adrian - No cash for anyone at first rounds! ( well, only the default. )
|
|
// Get the cash bonus awarded for completing the round
|
|
int RoundBonus = m_bCompleteReset ? 0 : mp_afterroundmoney.GetInt();
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
pPlayer->m_iNumSpawns = 0;
|
|
pPlayer->m_bTeamChanged = false;
|
|
|
|
// Award between round auto bonuses
|
|
if ( RoundBonus > 0 )
|
|
{
|
|
pPlayer->AddAccount( RoundBonus, false );
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
}
|
|
|
|
// Respawn entities (glass, doors, etc..). NOTE this also KILLS any map entities that the players happen to be pointing to.
|
|
// For this reason, there is a UpdateMapEntityPointers() call to each player after the restart_round event is issued and processed.
|
|
CleanUpMap();
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
m_coopPlayersInDeploymentZone = true;
|
|
|
|
// output to the game entity here that says, we completed the room
|
|
// and open the door to the next room
|
|
CGameCoopMissionManager *pManager = GetCoopMissionManager();
|
|
// we really dont have one
|
|
if ( !pManager )
|
|
{
|
|
DevMsg( "Coop mission map is missing a game_coopmission_manager entity. You can't keep track of completed waves without it!\n" );
|
|
}
|
|
else
|
|
{
|
|
pManager->SetRoundReset();
|
|
// we do it here after we set a bunch or things for the map,
|
|
// because coop might want a certain map type even if they aren't spawned right now
|
|
}
|
|
}
|
|
|
|
// Reduce hostage count to desired number
|
|
|
|
int iHostageCount = mp_hostages_max.GetInt();
|
|
|
|
if ( ( mp_hostages_max.GetInt() != atoi( mp_hostages_max.GetDefault() ) ) &&
|
|
( g_pMapInfo ) &&
|
|
( g_pMapInfo->m_iHostageCount != 0 ) )
|
|
{
|
|
iHostageCount = g_pMapInfo->m_iHostageCount;
|
|
}
|
|
|
|
if ( g_Hostages.Count() > iHostageCount )
|
|
{
|
|
CUtlVector< CHostage * > arrCopyOfOriginalHostageIndices;
|
|
arrCopyOfOriginalHostageIndices.AddMultipleToTail( g_Hostages.Count(), g_Hostages.Base() );
|
|
|
|
if ( !mp_hostages_spawn_same_every_round.GetBool() )
|
|
m_arrSelectedHostageSpawnIndices.RemoveAll();
|
|
|
|
if ( m_arrSelectedHostageSpawnIndices.Count() )
|
|
{
|
|
// We have pre-selected hostage indices, keep only them
|
|
FOR_EACH_VEC_BACK( g_Hostages, idxGlobalHostage )
|
|
{
|
|
if ( m_arrSelectedHostageSpawnIndices.Find( idxGlobalHostage ) != m_arrSelectedHostageSpawnIndices.InvalidIndex() )
|
|
continue;
|
|
CHostage *pHostage = g_Hostages[ idxGlobalHostage ];
|
|
UTIL_Remove( pHostage );
|
|
g_Hostages.Remove( idxGlobalHostage );
|
|
|
|
}
|
|
}
|
|
else if ( mp_hostages_spawn_force_positions.GetString()[0] )
|
|
{
|
|
CUtlVector< int > arrBestHostageIdx;
|
|
CUtlVector< char* > tagStrings;
|
|
V_SplitString( mp_hostages_spawn_force_positions.GetString(), ",", tagStrings );
|
|
arrBestHostageIdx.EnsureCapacity( tagStrings.Count() );
|
|
FOR_EACH_VEC( tagStrings, iTagString )
|
|
{
|
|
arrBestHostageIdx.AddToTail( Q_atoi( tagStrings[iTagString] ) );
|
|
}
|
|
tagStrings.PurgeAndDeleteElements();
|
|
|
|
// Now we have selected best hostage indices, keep only them
|
|
FOR_EACH_VEC_BACK( g_Hostages, idxGlobalHostage )
|
|
{
|
|
if ( arrBestHostageIdx.Find( idxGlobalHostage ) != arrBestHostageIdx.InvalidIndex() )
|
|
continue;
|
|
CHostage *pHostage = g_Hostages[ idxGlobalHostage ];
|
|
UTIL_Remove( pHostage );
|
|
g_Hostages.Remove( idxGlobalHostage );
|
|
}
|
|
}
|
|
else if ( mp_hostages_spawn_farthest.GetBool() )
|
|
{
|
|
CUtlVector< int > arrBestHostageIdx;
|
|
vec_t bestMetric = 0;
|
|
CUtlVector< int > arrTryHostageIdx;
|
|
for ( int iStartIdx = 0; iStartIdx < mp_hostages_max.GetInt(); ++ iStartIdx )
|
|
arrTryHostageIdx.AddToTail( iStartIdx );
|
|
arrBestHostageIdx.AddMultipleToTail( arrTryHostageIdx.Count(), arrTryHostageIdx.Base() );
|
|
while ( 1 )
|
|
{
|
|
vec_t metricThisCombo = 0;
|
|
for ( int iFirstHostage = 0; iFirstHostage < arrTryHostageIdx.Count(); ++ iFirstHostage )
|
|
{
|
|
for ( int iSecondHostage = iFirstHostage + 1; iSecondHostage < arrTryHostageIdx.Count(); ++ iSecondHostage )
|
|
{
|
|
vec_t len2Dsq = ( g_Hostages[ arrTryHostageIdx[iFirstHostage] ]->GetAbsOrigin() - g_Hostages[ arrTryHostageIdx[iSecondHostage] ]->GetAbsOrigin() ).Length2DSqr();
|
|
metricThisCombo += len2Dsq;
|
|
}
|
|
}
|
|
|
|
if ( metricThisCombo > bestMetric )
|
|
{
|
|
arrBestHostageIdx.RemoveAll();
|
|
arrBestHostageIdx.AddMultipleToTail( arrTryHostageIdx.Count(), arrTryHostageIdx.Base() );
|
|
bestMetric = metricThisCombo;
|
|
}
|
|
|
|
// Advance to next permutation
|
|
int iAdvanceIdx = 0;
|
|
while ( ( iAdvanceIdx < arrTryHostageIdx.Count() ) && ( arrTryHostageIdx[ arrTryHostageIdx.Count() - 1 - iAdvanceIdx ] >= g_Hostages.Count() - 1 - iAdvanceIdx ) )
|
|
iAdvanceIdx ++;
|
|
if ( iAdvanceIdx >= arrTryHostageIdx.Count() )
|
|
break; // Cannot set a valid permutation
|
|
// Increment the index
|
|
arrTryHostageIdx[ arrTryHostageIdx.Count() - 1 - iAdvanceIdx ] ++;
|
|
// Set all the following indices
|
|
for ( int iFollowingIdx = arrTryHostageIdx.Count() - iAdvanceIdx; iFollowingIdx < arrTryHostageIdx.Count(); ++ iFollowingIdx )
|
|
arrTryHostageIdx[iFollowingIdx] = arrTryHostageIdx[ arrTryHostageIdx.Count() - 1 - iAdvanceIdx ] + ( iFollowingIdx - ( arrTryHostageIdx.Count() - iAdvanceIdx ) + 1 );
|
|
}
|
|
|
|
// Now we have selected best hostage indices, keep only them
|
|
FOR_EACH_VEC_BACK( g_Hostages, idxGlobalHostage )
|
|
{
|
|
if ( arrBestHostageIdx.Find( idxGlobalHostage ) != arrBestHostageIdx.InvalidIndex() )
|
|
continue;
|
|
CHostage *pHostage = g_Hostages[ idxGlobalHostage ];
|
|
UTIL_Remove( pHostage );
|
|
g_Hostages.Remove( idxGlobalHostage );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Enforce spawn exclusion groups
|
|
CUtlVector< CHostage * > arrSelectedSpawns;
|
|
while ( ( arrSelectedSpawns.Count() < mp_hostages_max.GetInt() ) && g_Hostages.Count() )
|
|
{
|
|
uint32 uiTotalSpawnWeightFactor = 0;
|
|
FOR_EACH_VEC( g_Hostages, idxGlobalHostage )
|
|
{
|
|
if ( CHostage *pCheckHostage = g_Hostages[idxGlobalHostage] )
|
|
uiTotalSpawnWeightFactor += pCheckHostage->GetHostageSpawnRandomFactor();
|
|
}
|
|
if ( !uiTotalSpawnWeightFactor )
|
|
break;
|
|
|
|
uint32 iKeepHostage = ( uint32 ) RandomInt( 0, uiTotalSpawnWeightFactor - 1 );
|
|
CHostage *pKeepHostage = NULL;
|
|
FOR_EACH_VEC( g_Hostages, idxGlobalHostage )
|
|
{
|
|
if ( CHostage *pCheckHostage = g_Hostages[idxGlobalHostage] )
|
|
{
|
|
uint32 uiThisFactor = pCheckHostage->GetHostageSpawnRandomFactor();
|
|
if ( iKeepHostage < uiThisFactor )
|
|
{
|
|
pKeepHostage = pCheckHostage;
|
|
g_Hostages.Remove( idxGlobalHostage );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
iKeepHostage -= uiThisFactor;
|
|
}
|
|
}
|
|
}
|
|
if ( !pKeepHostage )
|
|
break;
|
|
|
|
uint32 uiHostageSpawnExclusionGroup = pKeepHostage->GetHostageSpawnExclusionGroup();
|
|
arrSelectedSpawns.AddToTail( pKeepHostage );
|
|
|
|
if ( uiHostageSpawnExclusionGroup )
|
|
{
|
|
FOR_EACH_VEC_BACK( g_Hostages, idxGlobalHostage )
|
|
{
|
|
CHostage *pCheckHostage = g_Hostages[ idxGlobalHostage ];
|
|
if ( ( pCheckHostage != pKeepHostage ) && !!( pCheckHostage->GetHostageSpawnExclusionGroup() & uiHostageSpawnExclusionGroup ) )
|
|
{ // They share the same exclusion group
|
|
UTIL_Remove( pCheckHostage );
|
|
g_Hostages.Remove( idxGlobalHostage );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Remove all the remaining hostages that we didn't pick
|
|
while ( g_Hostages.Count() )
|
|
{
|
|
CHostage *pHostage = g_Hostages.Tail();
|
|
UTIL_Remove( pHostage );
|
|
g_Hostages.RemoveMultipleFromTail( 1 );
|
|
}
|
|
// Add back the hostages that we decided to keep
|
|
g_Hostages.AddMultipleToTail( arrSelectedSpawns.Count(), arrSelectedSpawns.Base() );
|
|
}
|
|
|
|
// Keep removing randomly now until we reach needed number of hostages remaining
|
|
while ( g_Hostages.Count() > mp_hostages_max.GetInt() )
|
|
{
|
|
int randHostage = RandomInt( 0, g_Hostages.Count() - 1 );
|
|
|
|
CHostage *pHostage = g_Hostages[ randHostage ];
|
|
UTIL_Remove( pHostage );
|
|
g_Hostages.Remove( randHostage );
|
|
}
|
|
|
|
// Remember which spots ended up picked, so that players could disable randomization and keep the spots
|
|
if ( !m_arrSelectedHostageSpawnIndices.Count() )
|
|
{
|
|
FOR_EACH_VEC( g_Hostages, iPickedHostage )
|
|
{
|
|
int idxOriginalSpawnPoint = arrCopyOfOriginalHostageIndices.Find( g_Hostages[iPickedHostage] );
|
|
Assert( idxOriginalSpawnPoint != arrCopyOfOriginalHostageIndices.InvalidIndex() );
|
|
m_arrSelectedHostageSpawnIndices.AddToTail( idxOriginalSpawnPoint );
|
|
}
|
|
}
|
|
|
|
// Show information about which hostage positions were selected for the round
|
|
CFmtStr fmtHostagePositions;
|
|
fmtHostagePositions.AppendFormat( "Selected %d hostage positions '", g_Hostages.Count() );
|
|
FOR_EACH_VEC( g_Hostages, iPickedHostage )
|
|
{
|
|
int idxOriginalSpawnPoint = arrCopyOfOriginalHostageIndices.Find( g_Hostages[iPickedHostage] );
|
|
Assert( idxOriginalSpawnPoint != arrCopyOfOriginalHostageIndices.InvalidIndex() );
|
|
fmtHostagePositions.AppendFormat( "%d,", idxOriginalSpawnPoint );
|
|
}
|
|
fmtHostagePositions.Access()[ fmtHostagePositions.Length() - 1 ] = '\'';
|
|
fmtHostagePositions.AppendFormat( "\n" );
|
|
ConMsg( "%s", fmtHostagePositions.Access() );
|
|
}
|
|
|
|
// this needs to happen before we cleanup the map or we won't have a mission manager!
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
m_nGuardianModeWaveNumber = 1;
|
|
m_nGuardianGrenadesToGiveBots = 0;
|
|
m_coopPlayersInDeploymentZone = true;
|
|
|
|
m_coopBonusCoinsFound = 0;
|
|
m_coopBonusPistolsOnly = true;
|
|
}
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
{
|
|
m_nGuardianModeWaveNumber = 1;
|
|
m_nGuardianGrenadesToGiveBots = 0;
|
|
|
|
extern ConVar sv_guardian_heavy_count;
|
|
m_nNumHeaviesToSpawn = sv_guardian_heavy_count.GetInt();
|
|
|
|
m_nGuardianModeSpecialKillsRemaining = mp_guardian_special_kills_needed.GetInt();
|
|
|
|
char szWepShortName[MAX_PATH];
|
|
V_strcpy_safe( szWepShortName, mp_guardian_special_weapon_needed.GetString() );
|
|
if ( V_strcmp( szWepShortName, "any" ) == 0 )
|
|
{
|
|
m_nGuardianModeSpecialWeaponNeeded = 0;
|
|
}
|
|
else
|
|
{
|
|
char pszWeaponClassname[MAX_PATH];
|
|
V_sprintf_safe( pszWeaponClassname, "weapon_%s", szWepShortName );
|
|
CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pszWeaponClassname );
|
|
if ( pItemDef && pItemDef->GetDefinitionIndex() != 0 )
|
|
{
|
|
m_nGuardianModeSpecialWeaponNeeded = pItemDef->GetDefinitionIndex();
|
|
}
|
|
else
|
|
{
|
|
// REI: This code-path doesn't seem to be used in the latest operation, and I'm not sure this is the behavior we want.
|
|
// The quest HUD doesn't handle this path, so leave a message in chat for it in case it is used.
|
|
// But I suggest that maybe in this case we should just fall back on the 'any weapon' code.
|
|
|
|
// we didn't find the weapon specified or it was intentially left blank
|
|
// send a message instead that says we need to survive the round
|
|
CBroadcastRecipientFilter filter;
|
|
if ( IsHostageRescueMap() )
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_GuardianModeSurviveRoundHostage" );
|
|
else
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_GuardianModeSurviveRound" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// [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;
|
|
|
|
// Now respawn all players
|
|
CUtlVector< CCSPlayer* > respawningPlayersList;
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if ( pPlayer->GetTeamNumber() == TEAM_CT && PlayerModelInfo::GetPtr()->IsCTClass( pPlayer->PlayerClass() ) )
|
|
{
|
|
// [tj] Increment CT count and check CT uniforms.
|
|
numberOfCts++;
|
|
if ( ctUniform == -1 )
|
|
{
|
|
ctUniform = pPlayer->PlayerClass();
|
|
}
|
|
else if ( pPlayer->PlayerClass() != ctUniform )
|
|
{
|
|
allCtsWearingSameUniform = false;
|
|
}
|
|
|
|
respawningPlayersList.AddToTail( pPlayer );
|
|
}
|
|
else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && PlayerModelInfo::GetPtr()->IsTClass( pPlayer->PlayerClass() ) )
|
|
{
|
|
// [tj] Increment terrorist count and check terrorist uniforms
|
|
numberOfTerrorists++;
|
|
if ( terroristUniform == -1 )
|
|
{
|
|
terroristUniform = pPlayer->PlayerClass();
|
|
}
|
|
else if ( pPlayer->PlayerClass() != terroristUniform )
|
|
{
|
|
allTerroristsWearingSameUniform = false;
|
|
}
|
|
|
|
respawningPlayersList.AddToTail( pPlayer );
|
|
}
|
|
else
|
|
{
|
|
pPlayer->ObserverRoundRespawn();
|
|
}
|
|
}
|
|
|
|
// Shuffle spawning players list (this is done to ensure that spawning players don't always spawn in at the same spawn point)
|
|
ShufflePlayerList( respawningPlayersList );
|
|
|
|
// [menglish] reset per-round achievement variables for each player
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if( pPlayer )
|
|
{
|
|
pPlayer->ResetRoundBasedAchievementVariables();
|
|
}
|
|
}
|
|
|
|
// Spawn the players
|
|
for ( int i = 0; i < respawningPlayersList.Count(); ++i )
|
|
{
|
|
respawningPlayersList[ i ]->RoundRespawn();
|
|
}
|
|
|
|
// move follower chickens
|
|
CBaseEntity *pNextChicken = NULL;
|
|
|
|
while ( ( pNextChicken = gEntList.FindEntityByClassname( pNextChicken, "chicken" ) ) != NULL )
|
|
{
|
|
CChicken * pChicken = dynamic_cast< CChicken* >( pNextChicken );
|
|
if ( pChicken && pChicken->GetLeader( ) )
|
|
{
|
|
if ( TheNavMesh )
|
|
{
|
|
CNavArea *pPlayerNav = TheNavMesh->GetNearestNavArea( pChicken->GetLeader( ) );
|
|
|
|
const float tooSmall = 15.0f;
|
|
|
|
if ( pPlayerNav && pPlayerNav->GetSizeX() > tooSmall && pPlayerNav->GetSizeY() > tooSmall )
|
|
{
|
|
{
|
|
pChicken->SetAbsOrigin( pPlayerNav->GetRandomPoint() );
|
|
}
|
|
}
|
|
}
|
|
|
|
pChicken->GetLeader( )->IncrementNumFollowers( ); // redo since this got cleared on player respawn
|
|
}
|
|
}
|
|
|
|
// Mark all QMM records as eligible for receiving money next round
|
|
FOR_EACH_MAP_FAST( m_mapQueuedMatchmakingPlayersData, idxQMMEntry )
|
|
{
|
|
m_mapQueuedMatchmakingPlayersData.Element( idxQMMEntry )->m_bReceiveNoMoneyNextRound = false;
|
|
}
|
|
|
|
|
|
// [tj] Award same uniform achievement for qualifying teams
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
#if(ALL_WEARING_SAME_UNIFORM_ACHIEVEMENT)
|
|
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);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// [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();
|
|
}
|
|
|
|
// reset Match Stats
|
|
if ( m_bCompleteReset )
|
|
{
|
|
for ( int r = 0; r < MAX_MATCH_STATS_ROUNDS; r++ )
|
|
{
|
|
m_iMatchStats_RoundResults.GetForModify( r ) = 0;
|
|
m_iMatchStats_PlayersAlive_T.GetForModify( r ) = 0x3F;
|
|
m_iMatchStats_PlayersAlive_CT.GetForModify( r ) = 0x3F;
|
|
}
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if( pPlayer )
|
|
{
|
|
for ( int r = 0; r < MAX_MATCH_STATS_ROUNDS; r++ )
|
|
{
|
|
|
|
pPlayer->m_iMatchStats_Damage.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_EquipmentValue.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_MoneySaved.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_KillReward.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_LiveTime.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_Deaths.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_Assists.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_HeadShotKills.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_Objective.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_CashEarned.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_UtilityDamage.GetForModify( r ) = 0;
|
|
pPlayer->m_iMatchStats_EnemiesFlashed.GetForModify( r ) = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Log the match starting event into server log
|
|
UTIL_LogPrintf( "World triggered \"Match_Start\" on \"%s\"\n", STRING( gpGlobals->mapname ) );
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_CT ) )
|
|
{
|
|
char const *szName = pTeam->GetClanName();
|
|
if ( szName && *szName )
|
|
{
|
|
UTIL_LogPrintf( "Team playing \"CT\": %s\n", szName );
|
|
}
|
|
}
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST ) )
|
|
{
|
|
char const *szName = pTeam->GetClanName();
|
|
if ( szName && *szName )
|
|
{
|
|
UTIL_LogPrintf( "Team playing \"TERRORIST\": %s\n", szName );
|
|
}
|
|
}
|
|
}
|
|
|
|
// now run a tkpunish check, after the map has been cleaned up
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if ( ( pPlayer->GetTeamNumber() == TEAM_CT && PlayerModelInfo::GetPtr()->IsCTClass( pPlayer->PlayerClass() )) ||
|
|
( pPlayer->GetTeamNumber() == TEAM_TERRORIST && PlayerModelInfo::GetPtr()->IsTClass( pPlayer->PlayerClass() ) ) )
|
|
{
|
|
pPlayer->CheckTKPunishment();
|
|
}
|
|
}
|
|
|
|
if (m_bMapHasBombTarget == true )
|
|
{
|
|
if ( !IsPlayingTraining() && !IsWarmupPeriod() && !IsPlayingGunGameProgressive()
|
|
&& !IsPlayingGunGameDeathmatch() && !CSGameRules()->IsPlayingCoopMission() )
|
|
{
|
|
GiveC4ToRandomPlayer(); // Give C4 to the terrorists
|
|
|
|
if ( mp_defuser_allocation.GetInt() == DefuserAllocation::Random )
|
|
GiveDefuserToRandomPlayer();
|
|
}
|
|
}
|
|
|
|
// Reset game variables
|
|
m_flIntermissionStartTime = 0;
|
|
m_flRestartRoundTime = 0.0;
|
|
m_timeUntilNextPhaseStarts = 0.0f;
|
|
m_iAccountTerrorist = m_iAccountCT = 0;
|
|
m_iHostagesRescued = 0;
|
|
m_iHostagesTouched = 0;
|
|
m_flCMMItemDropRevealStartTime = 0;
|
|
m_flCMMItemDropRevealEndTime = 0;
|
|
|
|
// [tj] reset flawless and lossless round related flags
|
|
m_bNoTerroristsKilled = true;
|
|
m_bNoCTsKilled = true;
|
|
m_bNoTerroristsDamaged = true;
|
|
m_bNoCTsDamaged = true;
|
|
m_bNoEnemiesKilled = true;
|
|
m_pFirstKill = NULL;
|
|
m_pFirstBlood = NULL;
|
|
|
|
m_pMVP = NULL;
|
|
|
|
m_bCanDonateWeapons = true;
|
|
|
|
// [dwenger] Reset rescue-related achievement values
|
|
m_iHostagesRemaining = 0;
|
|
m_bAnyHostageReached = false;
|
|
|
|
m_arrRescuers.RemoveAll();
|
|
|
|
m_hostageWasInjured = false;
|
|
m_hostageWasKilled = false;
|
|
|
|
m_iRoundWinStatus = WINNER_NONE;
|
|
m_eRoundWinReason = RoundEndReason_StillInProgress;
|
|
m_bTargetBombed = m_bBombDefused = false;
|
|
m_bCompleteReset = false;
|
|
m_flNextHostageAnnouncement = gpGlobals->curtime;
|
|
|
|
m_iHostagesRemaining = g_Hostages.Count();
|
|
|
|
m_flDMBonusStartTime = gpGlobals->curtime + random->RandomFloat( mp_dm_time_between_bonus_min.GetFloat(), mp_dm_time_between_bonus_max.GetFloat() );
|
|
m_flDMBonusTimeLength = random->RandomFloat( mp_dm_bonus_length_min.GetFloat(), mp_dm_bonus_length_max.GetFloat() );
|
|
m_unDMBonusWeaponLoadoutSlot = PickRandomWeaponForDMBonus();
|
|
m_bDMBonusActive = false;
|
|
m_bIsDroppingItems = false;
|
|
|
|
// fire global game event
|
|
event = gameeventmanager->CreateEvent( "round_start" );
|
|
if ( event )
|
|
{
|
|
event->SetInt("timelimit", m_iRoundTime );
|
|
event->SetInt("fraglimit", 0 );
|
|
event->SetInt( "priority", 6 ); // round_start
|
|
|
|
if ( m_bMapHasRescueZone )
|
|
{
|
|
event->SetString("objective","HOSTAGE RESCUE");
|
|
}
|
|
else if ( m_bMapHasEscapeZone )
|
|
{
|
|
event->SetString("objective","PRISON ESCAPE");
|
|
}
|
|
else if ( m_bMapHasBombTarget || m_bMapHasBombZone )
|
|
{
|
|
event->SetString("objective","BOMB TARGET");
|
|
}
|
|
else
|
|
{
|
|
event->SetString("objective","DEATHMATCH");
|
|
}
|
|
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
// clear out hits data
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
|
|
if ( pPlayer )
|
|
pPlayer->m_totalHitsOnServer = 0;
|
|
}
|
|
|
|
if ( IsWarmupPeriod() )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_announce_warmup" );
|
|
if ( event )
|
|
gameeventmanager->FireEvent( event );
|
|
|
|
#ifndef CLIENT_DLL
|
|
CheckForGiftsLeaderboardUpdate();
|
|
#endif
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
if ( m_match.GetRoundsPlayed() <= 0 )
|
|
{
|
|
CheckForGiftsLeaderboardUpdate();
|
|
}
|
|
#endif
|
|
|
|
// We need to reassign the player's pointers to entities that were killed during the map clean up but have been recreated since the round_start event was called.
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
continue;
|
|
pPlayer->UpdateMapEntityPointers();
|
|
}
|
|
|
|
m_iActiveAssassinationTargetMissionID = 0;
|
|
|
|
// [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 );
|
|
|
|
g_pPlayerResource->UpdatePlayerData();
|
|
g_EntitySpotting->UpdateSpottedEntities();
|
|
|
|
|
|
if ( bClearAccountsAfterHalftime && IsPlayingClassic() && HasHalfTime() )
|
|
{
|
|
AssignStartingMoneyToAllPlayers();
|
|
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
m_iLoserBonus = TeamCashAwardValue( TeamCashAward::LOSER_BONUS );
|
|
}
|
|
|
|
if ( !IsPlayingTraining() )
|
|
{
|
|
// should we show an announcement to declare that this round might be the last round?
|
|
if ( IsLastRoundBeforeHalfTime() )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_announce_last_round_half" );
|
|
if ( event )
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
else if ( IsLastRoundOfMatch() )
|
|
{
|
|
// don't send the final round event if one of the teams just won the round by clinching
|
|
int iNumWinsToClinch = GetNumWinsToClinch();
|
|
if ( m_match.GetCTScore() != iNumWinsToClinch && m_match.GetTerroristScore() != iNumWinsToClinch )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_announce_final" );
|
|
if ( event )
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
else if ( IsMatchPoint() )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_announce_match_point" );
|
|
if ( event )
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
// if a team voted to surrender and it passed at the end of a round and we went into halftime,
|
|
// switch the teams that need to surrender
|
|
if ( m_bSwitchingTeamsAtRoundReset )
|
|
{
|
|
OnTeamsSwappedAtRoundReset();
|
|
}
|
|
|
|
m_bSwitchingTeamsAtRoundReset = false;
|
|
|
|
// Unfreeze all players now that the round is starting
|
|
UnfreezeAllPlayers();
|
|
|
|
if ( m_bPlayerItemsHaveBeenDisplayed )
|
|
ClearItemsDroppedDuringMatch();
|
|
|
|
// Perform round-related processing at the point when the next round has just restarted
|
|
// (This line should be last in this function)
|
|
PostRestartRound();
|
|
}
|
|
|
|
// Perform round-related processing at the point when the next round has just restarted
|
|
void CCSGameRules::PostRestartRound( void )
|
|
{
|
|
if ( m_match.GetRoundsPlayed() < 1 )
|
|
{
|
|
// Ensure all spectating players are in correct mode at the beginning of the match
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->IsObserver() )
|
|
{
|
|
// Only process players in observer mode
|
|
int nMode = pPlayer->GetObserverMode();
|
|
|
|
if ( nMode != OBS_MODE_CHASE && nMode != OBS_MODE_IN_EYE )
|
|
{
|
|
// If the player is not in the chase or in-eye mode then force them to chase mode
|
|
nMode = OBS_MODE_CHASE;
|
|
}
|
|
|
|
// Build and send the command to ensure player is in a valid observer mode
|
|
char szCommand[ 32 ];
|
|
V_snprintf( szCommand, sizeof( szCommand ), "spec_mode %i", nMode );
|
|
|
|
CCommand cmd;
|
|
cmd.Tokenize( szCommand, kCommandSrcCode );
|
|
ClientCommand( pPlayer, cmd );
|
|
}
|
|
}
|
|
}
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_poststart" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void CCSGameRules::UnfreezeAllPlayers( void )
|
|
{
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->RemoveFlag( FL_FROZEN );
|
|
}
|
|
}
|
|
}
|
|
|
|
loadout_positions_t CCSGameRules::PickRandomWeaponForDMBonus( void )
|
|
{
|
|
return LOADOUT_POSITION_INVALID;
|
|
}
|
|
|
|
|
|
void CCSGameRules::AssignStartingMoneyToAllPlayers( void )
|
|
{
|
|
// Loop through all players and give them only the starting money
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if ( pPlayer->GetTeamNumber() == TEAM_CT || pPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
int amount_to_assign = -pPlayer->GetAccountBalance() + GetStartMoney();
|
|
|
|
pPlayer->AddAccount( amount_to_assign, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::GiveC4ToRandomPlayer()
|
|
{
|
|
// Don't give C4 if not everyone is in the game, we are going to restart or if the convar says we should not
|
|
bool bNeeded = false;
|
|
NeededPlayersCheck( bNeeded );
|
|
float timeToRestart = GetRoundRestartTime() - gpGlobals->curtime;
|
|
if ( !mp_give_player_c4.GetBool() || timeToRestart > 0.001f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CUtlVector<CCSPlayer*> candidates;
|
|
candidates.EnsureCapacity(MAX_PLAYERS);
|
|
|
|
// add all eligible terrorist candidates to a list
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
candidates.AddToTail(pPlayer);
|
|
}
|
|
}
|
|
|
|
// randomly shuffle the list; this will keep the selection random in case of ties
|
|
FOR_EACH_VEC(candidates, i)
|
|
{
|
|
V_swap(candidates[i], candidates[random->RandomInt( 0, candidates.Count() - 1)] );
|
|
}
|
|
|
|
// now sort the list
|
|
candidates.Sort(BombSortPredicate);
|
|
|
|
// give bomb to the first candidate
|
|
if ( candidates.Count() > 0 )
|
|
{
|
|
CCSPlayer *pPlayer = candidates[0];
|
|
Assert( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->IsAlive() );
|
|
|
|
pPlayer->GiveNamedItem( WEAPON_C4_CLASSNAME );
|
|
pPlayer->SelectItem( "weapon_c4" );
|
|
pPlayer->m_fLastGivenBombTime = gpGlobals->curtime;
|
|
|
|
//ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_Have_Bomb" );
|
|
}
|
|
|
|
m_bBombDropped = false;
|
|
}
|
|
|
|
static int DefuserSortPredicate(CCSPlayer * const *left, CCSPlayer * const *right)
|
|
{
|
|
// should we prioritize humans over bots?
|
|
if (cv_bot_defer_to_human_items.GetBool() )
|
|
{
|
|
if ( (*left)->IsBot() && !(*right)->IsBot() )
|
|
return 1;
|
|
|
|
if ( !(*left)->IsBot() && (*right)->IsBot() )
|
|
return -1;
|
|
}
|
|
|
|
if ( (*left)->m_fLastGivenDefuserTime < (*right)->m_fLastGivenDefuserTime )
|
|
return -1;
|
|
|
|
if ( (*left)->m_fLastGivenDefuserTime > (*right)->m_fLastGivenDefuserTime )
|
|
return +1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CCSGameRules::GiveDefuserToRandomPlayer()
|
|
{
|
|
int iDefusersToGive = 2;
|
|
CUtlVector<CCSPlayer*> candidates;
|
|
candidates.EnsureCapacity(MAX_PLAYERS);
|
|
|
|
// add all CT candidates to a list
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == TEAM_CT )
|
|
{
|
|
candidates.AddToTail(pPlayer);
|
|
}
|
|
}
|
|
|
|
// randomly shuffle the list; this will keep the selection random in case of ties
|
|
FOR_EACH_VEC(candidates, i)
|
|
{
|
|
V_swap(candidates[i], candidates[random->RandomInt( 0, candidates.Count() - 1)] );
|
|
}
|
|
|
|
// now sort the shuffled list into subgroups
|
|
candidates.Sort(DefuserSortPredicate);
|
|
|
|
// give defusers to the first N candidates
|
|
for ( int i = 0; i < iDefusersToGive && i < candidates.Count(); ++i )
|
|
{
|
|
CCSPlayer *pPlayer = candidates[i];
|
|
Assert( pPlayer && pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->IsAlive() );
|
|
pPlayer->GiveDefuser(false);
|
|
pPlayer->HintMessage( "#Hint_you_have_the_defuser", false, true );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::Think()
|
|
{
|
|
// NOTE: We are skipping calling CTeamplayRules::Think() and CMultiplayRules::Think()
|
|
// by calling CGameRules directly. Be aware of this.
|
|
CGameRules::Think();
|
|
// if ( m_DeferredCallQueue.Count() > 0 && gpGlobals->curtime > m_flDeferredCallDispatchTime )
|
|
// {
|
|
// m_DeferredCallQueue.CallQueued();
|
|
// }
|
|
|
|
//
|
|
// Check if connected players have bans on record
|
|
//
|
|
int nCooldownMode = sv_kick_players_with_cooldown.GetInt();
|
|
if ( ( nCooldownMode <= 0 ) && steamgameserverapicontext && steamgameserverapicontext->SteamGameServer() && steamgameserverapicontext->SteamGameServer()->BSecure() )
|
|
nCooldownMode = 1; // On VAC secure servers enforce global cooldowns
|
|
if ( ( nCooldownMode > 0 ) && CCSGameRules::sm_mapGcBanInformation.Count() )
|
|
{
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pBasePlayer = UTIL_PlayerByIndex( i );
|
|
if ( !pBasePlayer )
|
|
continue;
|
|
|
|
CSteamID steamID;
|
|
if ( pBasePlayer->GetSteamID( &steamID ) && steamID.IsValid() &&
|
|
steamID.GetAccountID() )
|
|
{
|
|
CCSGameRules::GcBanInformationMap_t::IndexType_t idx = CCSGameRules::sm_mapGcBanInformation.Find( steamID.GetAccountID() );
|
|
if ( idx != CCSGameRules::sm_mapGcBanInformation.InvalidIndex() )
|
|
{
|
|
CCSGameRules::CGcBanInformation_t &banInfo = CCSGameRules::sm_mapGcBanInformation.Element( idx );
|
|
if ((banInfo.m_dblExpiration > Plat_FloatTime()) && !EMsgGCCStrike15_v2_MatchmakingKickBanReason_IsGreen(banInfo.m_uiReason) &&
|
|
( ( nCooldownMode > 1 ) || EMsgGCCStrike15_v2_MatchmakingKickBanReason_IsGlobal( banInfo.m_uiReason ) ) )
|
|
{
|
|
// Kick this guy
|
|
Msg( "Kicking user %s (sv_kick_players_with_cooldown=%d)\n", pBasePlayer->GetPlayerName(), nCooldownMode );
|
|
|
|
if ( sv_kick_ban_duration.GetInt() > 0 )
|
|
{
|
|
// don't roll the kick command into this, it will fail on a lan, where kickid will go through
|
|
engine->ServerCommand( CFmtStr( "banid %d %d;", sv_kick_ban_duration.GetInt(), pBasePlayer->GetUserID() ) );
|
|
}
|
|
char const *szReasonForKick = "Player has competitive matchmaking cooldown";
|
|
switch ( banInfo.m_uiReason )
|
|
{
|
|
case k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_OfficialBan:
|
|
case k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_ChallengeNotification:
|
|
szReasonForKick = "Account is Untrusted";
|
|
break;
|
|
case k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_GsltViolation:
|
|
case k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_ConvictedForBehavior:
|
|
case k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_ConvictedForCheating:
|
|
szReasonForKick = "Account is Convicted";
|
|
break;
|
|
case k_EMsgGCCStrike15_v2_MatchmakingKickBanReason_NoUserSession:
|
|
szReasonForKick = INVALID_STEAM_TICKET;
|
|
break;
|
|
}
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid_ex %d %d %s\n", pBasePlayer->GetUserID(), ( g_pGameRules && ((CCSGameRules *) g_pGameRules)->IsPlayingOffline() ) ? 0 : 1,
|
|
szReasonForKick ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void ServerThinkReplayUploader();
|
|
ServerThinkReplayUploader();
|
|
|
|
if ( IsQueuedMatchmaking() )
|
|
{
|
|
CEngineHltvInfo_t engineHltv;
|
|
if ( engine->GetEngineHltvInfo( engineHltv ) && engineHltv.m_bBroadcastActive && engineHltv.m_bMasterProxy )
|
|
{
|
|
int numCurrentSpectators = engineHltv.m_numClients - engineHltv.m_numProxies + engineHltv.m_numExternalTotalViewers;
|
|
int numCurrentSpectatorsTV = engineHltv.m_numClients - engineHltv.m_numProxies;
|
|
int numCurrentSpectatorsLnk = engineHltv.m_numExternalLinkedViewers;
|
|
|
|
if ( numCurrentSpectators > int( m_numSpectatorsCountMax ) )
|
|
m_numSpectatorsCountMax = numCurrentSpectators;
|
|
if ( numCurrentSpectatorsTV > int( m_numSpectatorsCountMaxTV ) )
|
|
m_numSpectatorsCountMaxTV = numCurrentSpectatorsTV;
|
|
if ( numCurrentSpectatorsLnk > int( m_numSpectatorsCountMaxLnk ) )
|
|
m_numSpectatorsCountMaxLnk = numCurrentSpectatorsLnk;
|
|
}
|
|
}
|
|
|
|
// This fires begin_new_match once when a new match starts... there are other similar game events
|
|
// but they all get fired multiple times between ending and starting a new match. Since we're using
|
|
// this event to restart an OGS session we can't let it spam like that.
|
|
if ( !m_bHasMatchStarted && // Turned off when we go to intermission, turned on once we fire the even
|
|
m_match.GetPhase() != GAMEPHASE_MATCH_ENDED && // Don't try to run this while we're still in intermission
|
|
!IsWarmupPeriod() && // Don't start the match until the first round after warmup
|
|
m_flRestartRoundTime == 0.0f && // This awkwardly ensures we've done a RestartRound() and are currently still waiting on one
|
|
UTIL_HumansInGame( true, true ) > 0 ) // don't start until there is somebody on a team
|
|
{
|
|
|
|
IGameEvent * newMatch = gameeventmanager->CreateEvent( "begin_new_match" );
|
|
if( newMatch )
|
|
{
|
|
gameeventmanager->FireEvent( newMatch );
|
|
}
|
|
m_bHasMatchStarted = true;
|
|
m_fMatchStartTime = gpGlobals->curtime;
|
|
|
|
if ( m_bPlayerItemsHaveBeenDisplayed )
|
|
ClearItemsDroppedDuringMatch();
|
|
|
|
CCSPlayerResource *pResource = dynamic_cast< CCSPlayerResource * >( g_pPlayerResource );
|
|
if ( pResource )
|
|
pResource->ForcePlayersPickColors();
|
|
}
|
|
|
|
// Display autobalance messages if necessary
|
|
if ( m_fAutobalanceDisplayTime > 0.0f && gpGlobals->curtime > m_fAutobalanceDisplayTime )
|
|
{
|
|
if ( m_AutobalanceStatus == AutobalanceStatus::THIS_ROUND )
|
|
{
|
|
UTIL_ClientPrintFilter( m_AutoBalanceTraitors, HUD_PRINTCENTER, "#SFUI_Notice_Player_Balanced" );
|
|
UTIL_ClientPrintFilter( m_AutoBalanceLoyalists, HUD_PRINTCENTER, "#SFUI_Notice_Teams_Balanced" );
|
|
|
|
m_AutoBalanceTraitors.RemoveAllRecipients();
|
|
m_AutoBalanceLoyalists.RemoveAllRecipients();
|
|
}
|
|
else if ( m_AutobalanceStatus == AutobalanceStatus::NEXT_ROUND )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER,"#SFUI_Notice_Auto_Team_Balance_Next_Round");
|
|
}
|
|
|
|
m_fAutobalanceDisplayTime = 0.0f;
|
|
m_AutobalanceStatus = AutobalanceStatus::NONE;
|
|
}
|
|
|
|
//Update replicated variable for time till next match or half
|
|
if ( m_match.GetPhase() == GAMEPHASE_HALFTIME )
|
|
{
|
|
if ( mp_halftime_pausetimer.GetBool() )
|
|
{
|
|
//Delay m_flRestartRoundTime for as long as we're paused.
|
|
m_flRestartRoundTime += gpGlobals->curtime - m_flLastThinkTime;
|
|
}
|
|
|
|
m_timeUntilNextPhaseStarts = m_flRestartRoundTime - gpGlobals->curtime;
|
|
|
|
m_bIsDroppingItems = false;
|
|
|
|
// Can also implement mp_halftime_pausematch here
|
|
}
|
|
else if ( m_match.GetPhase() == GAMEPHASE_MATCH_ENDED )
|
|
{
|
|
float flIntermissionDuration = IsQueuedMatchmaking() ? MIN( mp_competitive_endofmatch_extra_time.GetFloat(), GetIntermissionDuration() ) : GetIntermissionDuration();
|
|
|
|
if ( m_ItemsPtrDroppedDuringMatch.Count() > 0 )
|
|
{
|
|
// synch up the server's list of items recieved during this match to the ones on every client
|
|
if ( !m_bPlayerItemsHaveBeenDisplayed && ( m_phaseChangeAnnouncementTime > 0 && gpGlobals->curtime > m_phaseChangeAnnouncementTime ) )
|
|
{
|
|
SendPlayerItemDropsToClient();
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "endmatch_cmm_start_reveal_items" );
|
|
if( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
//now delay the rematch/failed vote/etc stuff until we are done revealing the items dropped
|
|
// 1 second per drop + 2 extra seconds for looking
|
|
m_flCMMItemDropRevealStartTime = gpGlobals->curtime;
|
|
}
|
|
|
|
// make sure that the intermission time accounts for the number of items we're giving out
|
|
if ( m_flIntermissionStartTime &&
|
|
( (m_flIntermissionStartTime + flIntermissionDuration) < m_flCMMItemDropRevealStartTime + (GetCMMItemDropRevealDuration() + 4.0f) ) )
|
|
{
|
|
m_flIntermissionStartTime = ( m_flCMMItemDropRevealStartTime + (GetCMMItemDropRevealDuration() + 4.0f) ) - flIntermissionDuration;
|
|
}
|
|
}
|
|
|
|
if (m_bIsDroppingItems && (m_flIntermissionStartTime + mp_win_panel_display_time.GetInt() + 5.0f + sv_reward_drop_delay.GetFloat()) < gpGlobals->curtime && m_flCMMItemDropRevealEndTime < gpGlobals->curtime)
|
|
{
|
|
m_bIsDroppingItems = false;
|
|
|
|
CheckSetVoteTime();
|
|
}
|
|
|
|
if ( IsPlayingGunGameProgressive() )
|
|
{
|
|
m_timeUntilNextPhaseStarts = (m_flRestartRoundTime + flIntermissionDuration + GetCMMItemDropRevealDuration()) - gpGlobals->curtime;
|
|
}
|
|
else
|
|
{
|
|
m_timeUntilNextPhaseStarts = m_flIntermissionStartTime + flIntermissionDuration - gpGlobals->curtime;
|
|
}
|
|
|
|
if ( m_flRestartRoundTime > 0.0f && ( ( m_flRestartRoundTime - 3.0 ) <= gpGlobals->curtime ) && !m_bVoiceWonMatchBragFired )
|
|
{
|
|
// [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->IsAlive() && pPlayer->GetTeamNumber() == m_match.GetWinningTeam() )
|
|
{
|
|
// have someone on the winning team brag about winning over the radio
|
|
pPlayer->Radio( "WonRound", "", true );
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_bVoiceWonMatchBragFired = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_timeUntilNextPhaseStarts = 0.0f;
|
|
m_bVoiceWonMatchBragFired = false;
|
|
m_bIsDroppingItems = false;
|
|
}
|
|
|
|
//Check if it is time to make the phase change announcement
|
|
if ( m_phaseChangeAnnouncementTime > 0 && gpGlobals->curtime > m_phaseChangeAnnouncementTime )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "announce_phase_end" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
m_phaseChangeAnnouncementTime = 0.0f;
|
|
}
|
|
|
|
|
|
//Let all teams process
|
|
for ( int i = 0; i < GetNumberOfTeams(); i++ )
|
|
{
|
|
GetGlobalTeam( i )->Think();
|
|
}
|
|
|
|
// Update Team Clan Names periodically
|
|
if ( m_fNextUpdateTeamClanNamesTime <= gpGlobals->curtime )
|
|
{
|
|
m_fNextUpdateTeamClanNamesTime = gpGlobals->curtime + 2;
|
|
UpdateTeamPredictions();
|
|
UpdateTeamClanNames( TEAM_CT );
|
|
UpdateTeamClanNames( TEAM_TERRORIST );
|
|
}
|
|
|
|
///// Check game rules /////
|
|
if ( CheckGameOver() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// did somebody hit the fraglimit ?
|
|
if ( CheckFragLimit() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Restart if we were flagged to do so
|
|
if ( m_endMatchOnThink )
|
|
{
|
|
m_endMatchOnThink = false;
|
|
GoToIntermission();
|
|
return;
|
|
}
|
|
|
|
if ( !m_bBuyTimeEnded && IsBuyTimeElapsed() )
|
|
{
|
|
m_bBuyTimeEnded = true;
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "buytime_ended" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
//Check for clinch
|
|
int iNumWinsToClinch = GetNumWinsToClinch();
|
|
|
|
bool bTeamHasClinchedVictory = false;
|
|
bool bMatchHasEnded = false;
|
|
|
|
//Check for halftime switching
|
|
if ( m_match.GetPhase() == GAMEPHASE_PLAYING_FIRST_HALF )
|
|
{
|
|
//The number of rounds before halftime depends on the mode and the associated convar
|
|
int numRoundsBeforeHalftime =
|
|
GetOvertimePlaying()
|
|
? ( mp_maxrounds.GetInt() + ( 2*GetOvertimePlaying() - 1 )*( mp_overtime_maxrounds.GetInt() / 2 ) )
|
|
: ( mp_maxrounds.GetInt() / 2 );
|
|
|
|
//Finally, check for halftime
|
|
|
|
bool bhalftime = false;
|
|
if ( numRoundsBeforeHalftime > 0 )
|
|
{
|
|
if ( m_match.GetRoundsPlayed() >= numRoundsBeforeHalftime )
|
|
{
|
|
bhalftime = true;
|
|
}
|
|
}
|
|
// if maxrounds is 0 then the server is relying on mp_timelimit rather than mp_maxrounds.
|
|
else if ( ( GetMapRemainingTime() <= ( ( mp_timelimit.GetInt() * 60 ) / 2 ) ) && IsRoundOver() )
|
|
{
|
|
bhalftime = true;
|
|
}
|
|
|
|
if ( bhalftime )
|
|
{
|
|
m_match.SetPhase( GAMEPHASE_HALFTIME );
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
m_flRestartRoundTime = gpGlobals->curtime + mp_halftime_duration.GetFloat();
|
|
SwitchTeamsAtRoundReset();
|
|
FreezePlayers();
|
|
}
|
|
}
|
|
//Check for end of half-time match
|
|
else if ( m_match.GetPhase() == GAMEPHASE_PLAYING_SECOND_HALF )
|
|
{
|
|
//Check for clinch
|
|
if ( iNumWinsToClinch > 0 && ( HasHalfTime() && !IsPlayingTraining()) )
|
|
{
|
|
bTeamHasClinchedVictory = ( m_match.GetCTScore() >= iNumWinsToClinch ) || ( m_match.GetTerroristScore() >= iNumWinsToClinch );
|
|
}
|
|
|
|
//Finally, if there have enough rounds played, end the match
|
|
bool bEndMatch = false;
|
|
|
|
int numRoundToEndMatch = mp_maxrounds.GetInt() + GetOvertimePlaying()*mp_overtime_maxrounds.GetInt();
|
|
if ( numRoundToEndMatch > 0 )
|
|
{
|
|
if ( m_match.GetRoundsPlayed() >= numRoundToEndMatch || bTeamHasClinchedVictory )
|
|
{
|
|
bEndMatch = true;
|
|
}
|
|
}
|
|
else if ( GetMapRemainingTime() <= 0 && IsRoundOver() )
|
|
{
|
|
bEndMatch = true;
|
|
}
|
|
|
|
// Check if the match ended in a tie and needs overtime
|
|
if ( bEndMatch && mp_overtime_enable.GetBool() && !bTeamHasClinchedVictory )
|
|
{
|
|
bEndMatch = false;
|
|
|
|
m_match.GoToOvertime( 1 );
|
|
m_match.SetPhase( GAMEPHASE_HALFTIME );
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
m_flRestartRoundTime = gpGlobals->curtime + mp_halftime_duration.GetFloat();
|
|
// SwitchTeamsAtRoundReset(); -- don't switch teams, only switch at true halftimes
|
|
FreezePlayers();
|
|
}
|
|
|
|
if ( bEndMatch )
|
|
{
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
GoToIntermission();
|
|
bMatchHasEnded = true;
|
|
|
|
if ( bTeamHasClinchedVictory && m_match.GetRoundsPlayed() < numRoundToEndMatch )
|
|
{
|
|
// Send chat message to let players know why match is ending early
|
|
CRecipientFilter filter;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
|
|
if ( pPlayer && ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR || pPlayer->GetTeamNumber() == TEAM_CT || pPlayer->GetTeamNumber() == TEAM_TERRORIST ) )
|
|
{
|
|
filter.AddRecipient( pPlayer );
|
|
}
|
|
}
|
|
|
|
filter.MakeReliable();
|
|
|
|
if ( m_match.GetCTScore() > m_match.GetTerroristScore() )
|
|
{
|
|
// CTs have clinched the match
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_CTs_Clinched_Match" );
|
|
}
|
|
else
|
|
{
|
|
// Ts have clinched the match
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_Ts_Clinched_Match" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//If playing a non-halftime game, check the max rounds
|
|
else if ( m_match.GetPhase() == GAMEPHASE_PLAYING_STANDARD )
|
|
{
|
|
// Check for a clinch
|
|
if ( mp_maxrounds.GetInt() > 0 && !IsPlayingTraining() && mp_match_can_clinch.GetBool() )
|
|
{
|
|
bTeamHasClinchedVictory = ( m_match.GetCTScore() >= iNumWinsToClinch ) || ( m_match.GetTerroristScore() >= iNumWinsToClinch );
|
|
}
|
|
|
|
// End the match if ( ( maxrounds are used ) and ( we've reached maxrounds or clinched the game ) ) or ( we've exceeded timelimit )
|
|
if ( mp_maxrounds.GetInt() > 0 && !IsPlayingTraining() )
|
|
{
|
|
if ( m_match.GetRoundsPlayed() >= mp_maxrounds.GetInt() || bTeamHasClinchedVictory )
|
|
{
|
|
bMatchHasEnded = true;
|
|
}
|
|
}
|
|
else if ( GetMapRemainingTime() <= 0 && !IsPlayingTraining() && IsRoundOver() )
|
|
{
|
|
bMatchHasEnded = true;
|
|
}
|
|
|
|
if ( bMatchHasEnded == true )
|
|
{
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
GoToIntermission();
|
|
}
|
|
}
|
|
|
|
if ( IsWarmupPeriod() )
|
|
{
|
|
#ifdef GAME_DLL
|
|
if ( IsQueuedMatchmaking() )
|
|
{
|
|
// if all humans are present and warmup time left is greater than mp_warmuptime_all_players_connected, reduce warmup time to mp_warmuptime_all_players_connected
|
|
if ( ( UTIL_HumansInGame( true, false ) == ( int ) MatchmakingGameTypeGameMaxPlayers( MatchmakingGameTypeToGame( sm_QueuedServerReservation.game_type() ) ) )
|
|
&& ( mp_warmuptime_all_players_connected.GetFloat() > 0 ) && ( GetWarmupPeriodEndTime() - mp_warmuptime_all_players_connected.GetFloat() >= gpGlobals->curtime ) )
|
|
{
|
|
m_fWarmupPeriodStart = gpGlobals->curtime;
|
|
mp_warmuptime.SetValue( mp_warmuptime_all_players_connected.GetFloat() );
|
|
|
|
// notify players
|
|
CBroadcastRecipientFilter filter;
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_All_Players_Connected", mp_warmuptime_all_players_connected.GetString() );
|
|
}
|
|
}
|
|
|
|
if ( IsWarmupPeriodPaused() && ( GetWarmupPeriodEndTime() - 6 >= gpGlobals->curtime) ) // Ignore warmup pause if within 6s of end.
|
|
{
|
|
// push out the timers indefinitely.
|
|
m_fWarmupPeriodStart += gpGlobals->curtime - m_flLastThinkTime;
|
|
|
|
m_fWarmupNextChatNoticeTime += gpGlobals->curtime - m_flLastThinkTime;
|
|
}
|
|
|
|
if ( m_fWarmupNextChatNoticeTime < gpGlobals->curtime )
|
|
{
|
|
m_fWarmupNextChatNoticeTime = gpGlobals->curtime + 10;
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
|
|
if ( IsQueuedMatchmaking() && ( UTIL_HumansInGame(true, false) < ( int ) MatchmakingGameTypeGameMaxPlayers( MatchmakingGameTypeToGame( sm_QueuedServerReservation.game_type() ) ) ) )
|
|
{
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_Match_Will_Start_Waiting_Chat" );
|
|
}
|
|
else
|
|
{
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_Match_Will_Start_Chat" );
|
|
}
|
|
|
|
if ( !m_numGlobalGiftsGiven )
|
|
CheckForGiftsLeaderboardUpdate();
|
|
}
|
|
#endif
|
|
//bool bIsPlayingProgressive = CSGameRules() && CSGameRules()->IsPlayingGunGameProgressive();
|
|
|
|
extern ConVar mp_do_warmup_period;
|
|
|
|
if ( ( UTIL_HumansInGame( true, true ) > 0 && ( GetWarmupPeriodEndTime() - 5 < gpGlobals->curtime) ) || !mp_do_warmup_period.GetBool() )
|
|
{
|
|
extern ConVar mp_warmup_pausetimer;
|
|
mp_warmup_pausetimer.SetValue( 0 ); // Timer is unpausable within 5 seconds of its end.
|
|
|
|
if (GetWarmupPeriodEndTime() <= gpGlobals->curtime)
|
|
{
|
|
// when the warmup period ends, set the round to restart in 3 seconds
|
|
if (!m_bCompleteReset && !m_bGameRestart)
|
|
{
|
|
GetGlobalTeam( TEAM_CT )->ResetTeamLeaders();
|
|
GetGlobalTeam( TEAM_TERRORIST )->ResetTeamLeaders();
|
|
|
|
m_flRestartRoundTime = gpGlobals->curtime + g_flWarmupToFreezetimeDelay;/*4.0*/;
|
|
m_bCompleteReset = true;
|
|
m_bGameRestart = true;
|
|
FreezePlayers();
|
|
|
|
CCSPlayerResource *pResource = dynamic_cast< CCSPlayerResource * >( g_pPlayerResource );
|
|
if ( pResource )
|
|
pResource->ForcePlayersPickColors();
|
|
|
|
{
|
|
CReliableBroadcastRecipientFilter filter;
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#SFUI_Notice_Warmup_Has_Ended" );
|
|
m_fWarmupNextChatNoticeTime = gpGlobals->curtime + 10;
|
|
}
|
|
|
|
{
|
|
CReliableBroadcastRecipientFilter filter;
|
|
CCSUsrMsg_WarmupHasEnded msg;
|
|
SendUserMessage( filter, CS_UM_WarmupHasEnded, msg );
|
|
}
|
|
}
|
|
|
|
// when the round resets, turn off the warmup period
|
|
if ( m_flRestartRoundTime <= gpGlobals->curtime )
|
|
{
|
|
m_bWarmupPeriod = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for the end of the round.
|
|
if ( IsFreezePeriod() )
|
|
{
|
|
CheckFreezePeriodExpired();
|
|
}
|
|
else
|
|
{
|
|
CheckRoundTimeExpired();
|
|
}
|
|
|
|
CheckLevelInitialized();
|
|
|
|
if ( !m_bRoundTimeWarningTriggered && GetRoundRemainingTime() < ROUND_END_WARNING_TIME && !IsPlayingTraining( ) )
|
|
{
|
|
m_bRoundTimeWarningTriggered = true;
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_time_warning" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopMission() &&
|
|
m_flRestartRoundTime > 0.0f && ( ( m_flRestartRoundTime - 0.5 ) <= gpGlobals->curtime ) &&
|
|
!IsWarmupPeriod() && !m_bHasTriggeredCoopSpawnReset )
|
|
{
|
|
// we have to reset the spawns, BEFORE the PreRestartRound because that's where the spawns are initially set up
|
|
// and we have to let anought time for map logic to set them all up
|
|
CGameCoopMissionManager *pManager = GetCoopMissionManager();
|
|
// we really dont have one
|
|
if ( !pManager )
|
|
{
|
|
DevMsg( "Coop mission map is missing a game_coopmission_manager entity. You can't keep track of completed waves without it!\n" );
|
|
}
|
|
else
|
|
{
|
|
pManager->SetSpawnsReset();
|
|
}
|
|
|
|
m_bHasTriggeredCoopSpawnReset = true;
|
|
}
|
|
|
|
if (m_flRestartRoundTime > 0.0f && ((m_flRestartRoundTime - 0.3) <= gpGlobals->curtime) && !m_bHasTriggeredRoundStartMusic)
|
|
{
|
|
// Perform round-related processing at the point when there is less than 1 second of "free play" to go before the round officially ends
|
|
// At this point the round winner has been determined and displayed to the players
|
|
PreRestartRound();
|
|
}
|
|
|
|
if ( m_flRestartRoundTime > 0.0f && m_flRestartRoundTime <= gpGlobals->curtime )
|
|
{
|
|
if ( IsWarmupPeriod() && m_match.GetPhase() != GAMEPHASE_MATCH_ENDED && GetWarmupPeriodEndTime() <= gpGlobals->curtime && UTIL_HumansInGame( false, true ) && m_flGameStartTime != 0 )
|
|
{
|
|
m_bCompleteReset = true;
|
|
m_flRestartRoundTime = gpGlobals->curtime + 1;
|
|
mp_restartgame.SetValue( 5 );
|
|
|
|
m_bWarmupPeriod = false;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// restart only if no bots are speaking, and if no people are watching replay.
|
|
// But still restart even when people are watching replay, if they've been delaying restart for over 10 seconds; we don't want a bug where someone can indefinitely delay a round by watching replays over and over
|
|
float flMaxRoundDelayDueToReplay = spec_replay_round_delay.GetFloat();
|
|
|
|
if ( !botSpeaking && ( gpGlobals->curtime > m_flRestartRoundTime + flMaxRoundDelayDueToReplay || !engine->AnyClientsInHltvReplayMode() ) )
|
|
{
|
|
m_bHasTriggeredRoundStartMusic = false;
|
|
m_bHasTriggeredCoopSpawnReset = false;
|
|
|
|
// Don't call RoundEnd() before the first round of a match
|
|
if (m_match.GetRoundsPlayed() > 0)
|
|
{
|
|
if (IsWarmupPeriod() &&
|
|
(GetWarmupPeriodEndTime() <= gpGlobals->curtime) &&
|
|
UTIL_HumansInGame(false, true))
|
|
{
|
|
m_bCompleteReset = true;
|
|
m_flRestartRoundTime = gpGlobals->curtime + 1;
|
|
mp_restartgame.SetValue(5);
|
|
|
|
m_bWarmupPeriod = false;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Perform round-related processing at the official end of round
|
|
RoundEnd();
|
|
}
|
|
}
|
|
|
|
if (IsPlayingGunGameTRBomb() && m_bGameRestart)
|
|
{
|
|
m_bGameRestart = false;
|
|
|
|
// Reset all the gun game demolition mode data
|
|
ClearGunGameData();
|
|
|
|
// Destroy primary and secondary weapons, as well as grenades
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex(i);
|
|
|
|
if (pPlayer)
|
|
{
|
|
pPlayer->DestroyWeapons(false);
|
|
pPlayer->GiveDefaultItems();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
// Destroy primary and secondary weapons, as well as grenades
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->DestroyWeapons( true );
|
|
pPlayer->GiveDefaultItems();
|
|
pPlayer->ResetAccount();
|
|
pPlayer->AddAccount( mp_startmoney.GetInt(), false, false, NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
IGameEvent * leaderboardEvent = gameeventmanager->CreateEvent("round_end_upload_stats");
|
|
if (leaderboardEvent)
|
|
{
|
|
gameeventmanager->FireEvent(leaderboardEvent);
|
|
}
|
|
|
|
// Save the round data information one more time when the round officially ends (overwriting the previously saved file)
|
|
if ( ( ( m_iRoundWinStatus == WINNER_TER ) || ( m_iRoundWinStatus == WINNER_CT ) )
|
|
&& Helper_mp_backup_round_IsEnabled() )
|
|
{
|
|
SaveRoundDataInformation( mp_backup_round_file_last.GetString() );
|
|
}
|
|
else
|
|
{
|
|
mp_backup_round_file_last.SetValue( "" );
|
|
}
|
|
|
|
// Perform round-related processing at the point when the next round is beginning
|
|
RestartRound();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( gpGlobals->curtime > m_tmNextPeriodicThink )
|
|
{
|
|
CheckRestartRound();
|
|
CheckRespawnWaves();
|
|
m_tmNextPeriodicThink = gpGlobals->curtime + 1.0;
|
|
}
|
|
|
|
if ( IsPlayingGunGameProgressive() )
|
|
{
|
|
// Perform any queued team moves
|
|
for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( clientIndex );
|
|
if ( pPlayer )
|
|
{
|
|
if ( ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) &&
|
|
( pPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) &&
|
|
( pPlayer->GetPendingTeamNumber() != pPlayer->GetTeamNumber() ) )
|
|
{
|
|
pPlayer->HandleCommand_JoinTeam( pPlayer->GetPendingTeamNumber() ) ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
{
|
|
if ( m_bBombPlanted )
|
|
{
|
|
//AddTeamAccount( TEAM_CT, TeamCashAward::WIN_BY_TIME_RUNNING_OUT_BOMB );
|
|
//TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
}
|
|
else if ( m_flCoopRespawnAndHealTime > -1 && m_flCoopRespawnAndHealTime <= gpGlobals->curtime )
|
|
{
|
|
// HACK
|
|
int nTeam = IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
|
|
//int nOtherTeam = IsHostageRescueMap() ? TEAM_CT : TEAM_TERRORIST;
|
|
|
|
// give health to all of the players on the other team
|
|
for ( int j = 1; j <= MAX_PLAYERS; j++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( j ) );
|
|
if ( pPlayer && pPlayer->GetTeamNumber() == nTeam )
|
|
{
|
|
if ( pPlayer->IsAlive() )
|
|
{
|
|
pPlayer->GiveHealthAndArmorForGuardianMode( true );
|
|
}
|
|
else
|
|
{
|
|
// respawn any dead CTs as well
|
|
if ( pPlayer->IsAlive() == false )
|
|
{
|
|
pPlayer->State_Transition( STATE_GUNGAME_RESPAWN );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_flCoopRespawnAndHealTime = -1;
|
|
}
|
|
}
|
|
|
|
// if ( IsPlayingCoopMission() )
|
|
// {
|
|
// if ( m_flCoopRespawnAndHealTime > -1 && m_flCoopRespawnAndHealTime <= gpGlobals->curtime )
|
|
// {
|
|
// int nTeam = TEAM_CT;
|
|
// // give health to all of the players on the other team
|
|
// for ( int j = 1; j <= MAX_PLAYERS; j++ )
|
|
// {
|
|
// CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( j ) );
|
|
// if ( pPlayer && pPlayer->GetTeamNumber() == nTeam )
|
|
// {
|
|
// // respawn any dead CTs as well
|
|
// if ( pPlayer->IsAlive() == false )
|
|
// {
|
|
//
|
|
// pPlayer->State_Transition( STATE_GUNGAME_RESPAWN );
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// m_flCoopRespawnAndHealTime = -1;
|
|
// }
|
|
// }
|
|
|
|
if (IsPlayingGunGameDeathmatch())
|
|
{
|
|
if ( m_flDMBonusStartTime != -1 && gpGlobals->curtime >= m_flDMBonusStartTime )
|
|
{
|
|
if ( m_bDMBonusActive && gpGlobals->curtime > (m_flDMBonusStartTime + m_flDMBonusTimeLength) )
|
|
{
|
|
// bonus time ended.....
|
|
m_bDMBonusActive = false;
|
|
m_unDMBonusWeaponLoadoutSlot = PickRandomWeaponForDMBonus();
|
|
// pick the new one if we have enough time in the round
|
|
if ( GetRoundRemainingTime() > (mp_dm_time_between_bonus_max.GetFloat() + mp_dm_bonus_length_max.GetFloat()) )
|
|
{
|
|
m_flDMBonusStartTime = gpGlobals->curtime + random->RandomFloat( mp_dm_time_between_bonus_min.GetFloat(), mp_dm_time_between_bonus_max.GetFloat() );
|
|
m_flDMBonusTimeLength = random->RandomFloat( mp_dm_bonus_length_min.GetFloat(), mp_dm_bonus_length_max.GetFloat() );
|
|
}
|
|
else if ( GetRoundRemainingTime() > (mp_dm_time_between_bonus_min.GetFloat() + mp_dm_bonus_length_min.GetFloat()) )
|
|
{
|
|
m_flDMBonusTimeLength = mp_dm_bonus_length_min.GetFloat();
|
|
m_flDMBonusStartTime = gpGlobals->curtime + (GetRoundRemainingTime() - m_flDMBonusTimeLength);
|
|
}
|
|
else
|
|
{
|
|
// we're not going to hit a bonus, just disable it
|
|
m_flDMBonusStartTime = -1;
|
|
}
|
|
}
|
|
|
|
// BONUS TIME!!!!
|
|
else if ( !m_bDMBonusActive && m_flDMBonusStartTime > 0 )
|
|
{
|
|
m_bDMBonusActive = true;
|
|
|
|
// reset bonus dm suicide limiter.
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ));
|
|
|
|
if( pPlayer )
|
|
pPlayer->m_bHasUsedDMBonusRespawn = false;
|
|
}
|
|
|
|
// CSWeaponID wepID = WEAPON_NONE;
|
|
//
|
|
// for ( int i = 0; i < GetItemSchema()->GetItemDefinitionCount(); i++ )
|
|
// {
|
|
// CCStrike15ItemDefinition *pItemDef = ( CCStrike15ItemDefinition * )GetItemSchema()->GetItemDefinitionByMapIndex( i );
|
|
//
|
|
// if ( pItemDef->GetDefaultLoadoutSlot() == m_unDMBonusWeaponLoadoutSlot )
|
|
// {
|
|
// wepID = WeaponIdFromString( pItemDef->GetDefinitionName() );
|
|
// break;
|
|
// }
|
|
// }
|
|
//
|
|
// const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo(wepID);
|
|
// const char *szWeaponName = pWeaponInfo ? pWeaponInfo->szPrintName : "";
|
|
//
|
|
// char szSec[32];
|
|
// Q_snprintf(szSec, sizeof(szSec), "%d", (int)m_flDMBonusTimeLength);
|
|
//
|
|
// CReliableBroadcastRecipientFilter filter;
|
|
// UTIL_ClientPrintFilter(filter, HUD_PRINTTALK, "#SFUI_Notice_DM_BonusWeaponText", szSec, szWeaponName);
|
|
|
|
IGameEvent *bonusWeaponEvent = gameeventmanager->CreateEvent( "dm_bonus_weapon_start" );
|
|
if( bonusWeaponEvent )
|
|
{
|
|
bonusWeaponEvent->SetInt("time", (int)m_flDMBonusTimeLength);
|
|
// bonusWeaponEvent->SetInt("wepID", wepID);
|
|
bonusWeaponEvent->SetInt("Pos", m_unDMBonusWeaponLoadoutSlot );
|
|
gameeventmanager->FireEvent(bonusWeaponEvent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_flLastThinkTime = gpGlobals->curtime;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
|
|
void ServerThinkReplayUploader()
|
|
{
|
|
}
|
|
|
|
|
|
bool CCSGameRules::CheckGameOver()
|
|
{
|
|
if ( g_fGameOver ) // someone else quit the game already
|
|
{
|
|
// [Forrest] Calling ChangeLevel multiple times was causing IncrementMapCycleIndex
|
|
// to skip over maps in the list. Avoid this using a technique from CTeamplayRoundBasedRules::Think.
|
|
|
|
//
|
|
// In queue matchmaking mode we let users vote for rematch
|
|
//
|
|
if ( IsQueuedMatchmaking() )
|
|
{
|
|
extern ConVar mp_match_restart_delay;
|
|
switch ( m_eQueuedMatchmakingRematchState )
|
|
{
|
|
case k_EQueuedMatchmakingRematchState_MatchInProgress:
|
|
// give the clients several seconds for the scoreboard to be displayed
|
|
if ( gpGlobals->curtime < m_flIntermissionStartTime + MAX( mp_match_restart_delay.GetInt(), ( m_flCMMItemDropRevealStartTime - m_flIntermissionStartTime + (GetCMMItemDropRevealDuration() + 4.0f) ) ) )
|
|
return true;
|
|
// else fallthrough
|
|
|
|
case k_EQueuedMatchmakingRematchState_VoteStarting:
|
|
{
|
|
|
|
#if VALVE_COMPETITIVE_REMATCH_FEATURE_ENABLED
|
|
char const *szCannotRematchExplanation = "#SFUI_vote_failed_rematch_explain";
|
|
static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
|
|
if ( m_pQueuedMatchmakingReportedRoundStats )
|
|
{
|
|
uint32 numHumansPresent = 0;
|
|
for ( int k = 0; k < m_pQueuedMatchmakingReportedRoundStats->pings().size(); ++ k )
|
|
{
|
|
if ( m_pQueuedMatchmakingReportedRoundStats->pings(k) > 0 )
|
|
++ numHumansPresent;
|
|
}
|
|
if ( numHumansPresent == m_numQueuedMatchmakingAccounts )
|
|
szCannotRematchExplanation = NULL;
|
|
}
|
|
szCannotRematchExplanation = "#SFUI_vote_failed_rematch_mmsvr";
|
|
if ( !s_pchTournamentServer && !szCannotRematchExplanation )
|
|
{
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteStarting;
|
|
engine->ServerCommand( "callvote rematch;\n" );
|
|
}
|
|
else
|
|
{
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematchFailed;
|
|
|
|
if ( !s_pchTournamentServer )
|
|
{
|
|
// Send chat message to let players know why match cannot proceed
|
|
CRecipientFilter filter;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
|
|
if ( pPlayer && ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR || pPlayer->GetTeamNumber() == TEAM_CT || pPlayer->GetTeamNumber() == TEAM_TERRORIST ) )
|
|
{
|
|
filter.AddRecipient( pPlayer );
|
|
}
|
|
}
|
|
|
|
filter.MakeReliable();
|
|
|
|
// CTs have surrendered
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, szCannotRematchExplanation );
|
|
}
|
|
}
|
|
#else // VALVE_COMPETITIVE_REMATCH_FEATURE_ENABLED
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematchFailed;
|
|
#endif // VALVE_COMPETITIVE_REMATCH_FEATURE_ENABLED
|
|
}
|
|
return true;
|
|
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematchInProgress:
|
|
// waiting for the vote outcome
|
|
return true;
|
|
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematch_CT_Surrender:
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematch_T_Surrender:
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematch_Aborted:
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematchFailed;
|
|
// fall through:
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematchFailed:
|
|
// This condition will then cause all people to disconnect
|
|
if ( m_flIntermissionStartTime + MIN( mp_competitive_endofmatch_extra_time.GetFloat(), GetIntermissionDuration() ) < gpGlobals->curtime + mp_competitive_endofmatch_extra_time.GetFloat() )
|
|
m_flIntermissionStartTime = ( gpGlobals->curtime + mp_competitive_endofmatch_extra_time.GetFloat() ) - MIN( mp_competitive_endofmatch_extra_time.GetFloat(), GetIntermissionDuration() );
|
|
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematchFailed_Done;
|
|
|
|
{
|
|
CReliableBroadcastRecipientFilter filter;
|
|
CCSUsrMsg_ServerRankRevealAll msg;
|
|
static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
|
|
if ( s_pchTournamentServer )
|
|
msg.set_seconds_till_shutdown( MAX( 0, mp_competitive_endofmatch_extra_time.GetInt() ) );
|
|
SendUserMessage( filter, CS_UM_ServerRankRevealAll, msg );
|
|
}
|
|
break;
|
|
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematchSucceeded:
|
|
#if VALVE_COMPETITIVE_REMATCH_FEATURE_ENABLED
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematch_Done;
|
|
#else // VALVE_COMPETITIVE_REMATCH_FEATURE_ENABLED
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematchFailed;
|
|
#endif // VALVE_COMPETITIVE_REMATCH_FEATURE_ENABLED
|
|
break; // let the normal logic proceed and change level
|
|
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematch_Done:
|
|
break; // let the normal logic proceed and change level
|
|
case k_EQueuedMatchmakingRematchState_VoteToRematchFailed_Done:
|
|
if ( IsQueuedMatchmaking() && ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematchFailed_Done )
|
|
&& m_flIntermissionStartTime && ( m_flIntermissionStartTime + MIN( mp_competitive_endofmatch_extra_time.GetFloat(), GetIntermissionDuration() ) < gpGlobals->curtime ) )
|
|
{
|
|
// Tell all clients to return to the lobby
|
|
CReliableBroadcastRecipientFilter filter;
|
|
CCSUsrMsg_DisconnectToLobby msg;
|
|
SendUserMessage( filter, CS_UM_DisconnectToLobby, msg );
|
|
}
|
|
break; // let the normal logic proceed and change level
|
|
}
|
|
}
|
|
|
|
// if we're in a "season" map group, let players vote to pick the next map at the end of the match
|
|
if ( IsEndMatchVotingForNextMap() )
|
|
{
|
|
extern ConVar mp_match_restart_delay;
|
|
switch ( m_eEndMatchMapVoteState )
|
|
{
|
|
case k_EEndMatchMapVoteState_MatchInProgress:
|
|
// give the clients several seconds for the scoreboard to be displayed
|
|
if ( gpGlobals->curtime < m_flIntermissionStartTime + 5.0f )
|
|
return true;
|
|
// else fallthrough
|
|
|
|
case k_EEndMatchMapVoteState_VoteInProgress:
|
|
{
|
|
// waiting for the vote outcome
|
|
CCSPlayerResource *pResource = dynamic_cast< CCSPlayerResource * >( g_pPlayerResource );
|
|
if ( pResource && pResource->EndMatchNextMapAllVoted() )
|
|
{
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_AllPlayersVoted;
|
|
}
|
|
|
|
if ( (m_flIntermissionStartTime + GetIntermissionDuration()) < gpGlobals->curtime )
|
|
{
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_VoteTimeEnded;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case k_EEndMatchMapVoteState_VoteTimeEnded:
|
|
case k_EEndMatchMapVoteState_AllPlayersVoted:
|
|
{
|
|
// first find out if we have a tie in the vote
|
|
// this is done in three different places
|
|
// TODO: make a function out of this
|
|
typedef int NumberOfVotes_t;
|
|
typedef int MapInCollection_t;
|
|
CUtlMap< MapInCollection_t, NumberOfVotes_t > mapMaps2Votes;
|
|
mapMaps2Votes.SetLessFunc( DefLessFunc( int ) );
|
|
|
|
// ask all players what they voted for and store it
|
|
// someone voted for a map in the mapgroup, see if we've recorded a vote for this map yet
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
#if 0 // #ifdef _DEBUG // cause everybody even bots that didn't vote to random vote in debug
|
|
if ( pPlayer )
|
|
{
|
|
int nVoteNum = pPlayer->GetEndMatchNextMapVote();
|
|
if ( nVoteNum < 0 )
|
|
nVoteNum = RandomInt( 0, MAX_ENDMATCH_VOTE_PANELS - 1 );
|
|
#else
|
|
if ( pPlayer && !pPlayer->IsBot() && ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) )
|
|
{
|
|
// players store the keybind list index that displays on the client
|
|
int nVoteNum = pPlayer->GetEndMatchNextMapVote();
|
|
#endif
|
|
if ( (nVoteNum < 0) || (nVoteNum >= MAX_ENDMATCH_VOTE_PANELS) || (m_nEndMatchMapGroupVoteOptions[nVoteNum] < 0) )
|
|
{
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: player#%d (%s) voted %d which is ignored\n", pPlayer->entindex(), pPlayer->GetPlayerName(), nVoteNum );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// convert the player vote to the map index
|
|
int nDesiredMapIndex = m_nEndMatchMapGroupVoteOptions[nVoteNum];
|
|
if ( nDesiredMapIndex < 0 )
|
|
continue;
|
|
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: player#%d (%s) voted %d (map=%d)\n", pPlayer->entindex(), pPlayer->GetPlayerName(), nVoteNum, nDesiredMapIndex );
|
|
}
|
|
|
|
// store the number of votes sorted by the map index
|
|
// someone voted for a map in the mapgroup, see if we've recorded a vote for this map yet
|
|
CUtlMap< MapInCollection_t, NumberOfVotes_t >::IndexType_t iMapEntry = mapMaps2Votes.Find( nDesiredMapIndex );
|
|
if ( iMapEntry == mapMaps2Votes.InvalidIndex() )
|
|
mapMaps2Votes.Insert( nDesiredMapIndex, 1 ); // we don't have a vote for this map, so insert one
|
|
else
|
|
mapMaps2Votes.Element( iMapEntry ) ++; // someone voted for this map already so increment it by one
|
|
}
|
|
}
|
|
|
|
// now takes the votes and do this so we can sort them my the total number of votes for each map in ascending order
|
|
CUtlMap< NumberOfVotes_t, MapInCollection_t > mapVotesInOrder;
|
|
mapVotesInOrder.SetLessFunc( DefLessFunc( int ) );
|
|
FOR_EACH_MAP( mapMaps2Votes, itMap2Vote )
|
|
{
|
|
mapVotesInOrder.Insert( mapMaps2Votes.Element( itMap2Vote ), mapMaps2Votes.Key( itMap2Vote ) );
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: Total %d votes for map %d\n", mapMaps2Votes.Element( itMap2Vote ), mapMaps2Votes.Key( itMap2Vote ) );
|
|
}
|
|
}
|
|
|
|
// clear the tie votes in our global list
|
|
m_nEndMatchTiedVotes.RemoveAll();
|
|
for ( CUtlMap< NumberOfVotes_t, MapInCollection_t >::IndexType_t idxVotesInOrder = mapVotesInOrder.LastInorder();
|
|
idxVotesInOrder != mapVotesInOrder.InvalidIndex(); idxVotesInOrder = mapVotesInOrder.PrevInorder( idxVotesInOrder ) )
|
|
{
|
|
// compare the first to the last in the list and add any to it that matches because we have a tie
|
|
if ( mapVotesInOrder.Key( idxVotesInOrder ) == mapVotesInOrder.Key( mapVotesInOrder.LastInorder() ) )
|
|
{
|
|
m_nEndMatchTiedVotes.AddToTail( mapVotesInOrder.Element( idxVotesInOrder ) );
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: Considering best map %d with %d votes\n", mapVotesInOrder.Element( idxVotesInOrder ), mapVotesInOrder.Key( idxVotesInOrder ) );
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
m_nEndMatchMapVoteWinner = -1;
|
|
// if we don't have any ties at all, it means no one voted, so add all of the maps to the "tie" list
|
|
if ( !m_nEndMatchTiedVotes.Count() )
|
|
{
|
|
for ( int iVoteOption = 0; iVoteOption < MAX_ENDMATCH_VOTE_PANELS; ++ iVoteOption )
|
|
{
|
|
if ( m_nEndMatchMapGroupVoteOptions[iVoteOption] >= 0 )
|
|
{
|
|
m_nEndMatchTiedVotes.AddToTail( m_nEndMatchMapGroupVoteOptions[iVoteOption] );
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: No votes considering map %d\n", m_nEndMatchMapGroupVoteOptions[iVoteOption] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if there is only one in the list, it means at least one player votes, but one of the maps is a clear winner
|
|
if ( m_nEndMatchTiedVotes.Count() == 1 )
|
|
{
|
|
m_nEndMatchMapVoteWinner = m_nEndMatchTiedVotes.Head();
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: Absolute winner map %d\n", m_nEndMatchMapVoteWinner );
|
|
}
|
|
}
|
|
|
|
if ( m_nEndMatchMapVoteWinner == -1 )
|
|
{
|
|
// if we have a tie, goto SelectingWinner and tell the client to do something
|
|
m_flIntermissionStartTime = MAX( m_flIntermissionStartTime, ( (gpGlobals->curtime + 4.0f) - (GetIntermissionDuration()) ) );
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_SelectingWinner;
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "endmatch_mapvote_selecting_map" );
|
|
if( event )
|
|
{
|
|
// we want to send all of the "ties" to the client. We send ten because technically we could have a 10 person server with each player voting for different maps
|
|
event->SetInt( "count", m_nEndMatchTiedVotes.Count() );
|
|
char sz[16];
|
|
for ( int i = 0 ; i < m_nEndMatchTiedVotes.Count() ; ++i)
|
|
{
|
|
V_sprintf_safe( sz, "slot%d", i+1 );
|
|
|
|
// since we store the index in the map group, we need to translate that to the index in the voting panel for the client before we send it down
|
|
for ( int j = 0 ; j < MAX_ENDMATCH_VOTE_PANELS ; ++j)
|
|
{
|
|
if ( m_nEndMatchTiedVotes[i] == m_nEndMatchMapGroupVoteOptions[j] )
|
|
{
|
|
event->SetInt( sz, j );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// else just pick the winner and move on
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_SettingNextLevel;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case k_EEndMatchMapVoteState_SelectingWinner:
|
|
{
|
|
if ( m_flIntermissionStartTime + GetIntermissionDuration() < gpGlobals->curtime )
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_SettingNextLevel;
|
|
}
|
|
return true;
|
|
|
|
case k_EEndMatchMapVoteState_SettingNextLevel:
|
|
{
|
|
m_flIntermissionStartTime = MAX( m_flIntermissionStartTime, ( (gpGlobals->curtime + 5.0f) - GetIntermissionDuration() ) );
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_VoteAllDone;
|
|
|
|
if ( g_pGameTypes )
|
|
{
|
|
const char* mapGroupName = gpGlobals->mapGroupName.ToCStr();
|
|
const CUtlStringList* mapsInGroup = g_pGameTypes->GetMapGroupMapList( mapGroupName );
|
|
|
|
if ( mapsInGroup )
|
|
{
|
|
int nNumMaps = mapsInGroup->Count();
|
|
// we don't have a clear winner
|
|
if ( m_nEndMatchMapVoteWinner == -1 )
|
|
{
|
|
// if we have a tie for first place, randomly pick between the ones in the lead
|
|
m_nEndMatchMapVoteWinner = m_nEndMatchTiedVotes[ RandomInt( 0, (m_nEndMatchTiedVotes.Count()-1) ) ];
|
|
if ( mp_verbose_changelevel_spew.GetInt() >= 2 )
|
|
{
|
|
Msg( "NEXTLEVELVOTE: Randomly picked winner map=%d from %d tied for nextlevel\n", m_nEndMatchMapVoteWinner, m_nEndMatchTiedVotes.Count() );
|
|
}
|
|
}
|
|
|
|
if ( m_nEndMatchMapVoteWinner < 0 || m_nEndMatchMapVoteWinner >= nNumMaps )
|
|
return true;
|
|
|
|
const char* internalMapName = (*mapsInGroup)[m_nEndMatchMapVoteWinner];
|
|
if ( internalMapName && V_strcmp( "undefined", internalMapName ) != 0 && V_strlen( internalMapName ) > 0 )
|
|
{
|
|
engine->ServerCommand( CFmtStr( "nextlevel %s;", internalMapName ) );
|
|
extern ConVar nextmap_print_enabled;
|
|
if ( nextmap_print_enabled.GetBool() )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "#game_nextmap", V_GetFileName( internalMapName ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case k_EEndMatchMapVoteState_VoteAllDone:
|
|
{
|
|
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
// check to see if we should change levels now
|
|
if ( m_flIntermissionStartTime && ( m_flIntermissionStartTime + GetIntermissionDuration() < gpGlobals->curtime ) )
|
|
{
|
|
IGameEvent * leaderboardEvent = gameeventmanager->CreateEvent( "round_end_upload_stats" );
|
|
if( leaderboardEvent )
|
|
{
|
|
gameeventmanager->FireEvent( leaderboardEvent );
|
|
}
|
|
|
|
if ( !IsPlayingTraining() )
|
|
{
|
|
// Perform round-related processing at the official end of round
|
|
RoundEnd();
|
|
}
|
|
|
|
// get the next map name
|
|
char szNextMap[MAX_PATH];
|
|
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
|
|
{
|
|
if ( mp_verbose_changelevel_spew.GetBool() )
|
|
{
|
|
Msg( "CHANGELEVEL: ConVar '%s' is set, next map will be '%s'\n", nextlevel.GetName(), nextlevel.GetString() );
|
|
}
|
|
|
|
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
|
|
}
|
|
else
|
|
{
|
|
GetNextLevelName( szNextMap, sizeof(szNextMap) );
|
|
}
|
|
|
|
// intermission is over
|
|
// check to see if we should restart or change maps
|
|
// mp_match_end_restart trumps everything and just restarts the current map regardless of next map
|
|
// mp_match_end_changelevel will force a changelevel even if next map matches current map
|
|
// otherwise if the next map is the same one we are on, don't reload it, just restart the map instead
|
|
// if the next map is different then changelevel
|
|
bool bNextLevelDiffers = !!Q_strcmp( szNextMap, STRING( gpGlobals->mapname ) );
|
|
bool bWantsChangeLevel = !mp_match_end_restart.GetBool() && ( mp_match_end_changelevel.GetBool() || bNextLevelDiffers );
|
|
if ( bWantsChangeLevel == false && mp_verbose_changelevel_spew.GetBool() )
|
|
{
|
|
Msg( "CHANGELEVEL: Not changing level, %s is false, %s is %s %s\n", mp_match_end_restart.GetName(), mp_match_end_changelevel.GetName(), mp_match_end_changelevel.GetBool() ? "true" : "false", !bNextLevelDiffers ? "and next is the same" : "" );
|
|
}
|
|
#if defined GAME_DLL
|
|
if ( engine->IsDedicatedServer() && DedicatedServerWorkshop().CurrentLevelNeedsUpdate() )
|
|
{
|
|
bWantsChangeLevel = true; // if current level is out of date, we need to change level no matter the settings.
|
|
}
|
|
#endif
|
|
|
|
if ( IsQueuedMatchmaking() && ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematchFailed_Done ) )
|
|
{
|
|
// Tell all clients to return to the lobby
|
|
CReliableBroadcastRecipientFilter filter;
|
|
CCSUsrMsg_DisconnectToLobby msg;
|
|
SendUserMessage( filter, CS_UM_DisconnectToLobby, msg );
|
|
}
|
|
else if ( bWantsChangeLevel )
|
|
{
|
|
ChangeLevel();
|
|
}
|
|
else
|
|
{
|
|
//Sometimes we don't want the new match the allow players to pick teams, such as in the case of a vote to scramble or swap teams.
|
|
if ( m_bPickNewTeamsOnReset )
|
|
{
|
|
// Clear the players to unassigned teams.
|
|
for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( clientIndex );
|
|
if(pPlayer)
|
|
{
|
|
pPlayer->ChangeTeam( TEAM_UNASSIGNED );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restart the round with a complete reset that will clear all the match status such as team scores.
|
|
m_bCompleteReset = true;
|
|
// clear the next level
|
|
nextlevel.SetValue( "" );
|
|
RestartRound();
|
|
g_fGameOver = false;
|
|
|
|
// Send an event that clients can key off of to update their UI state.
|
|
IGameEvent *restartEvent = gameeventmanager->CreateEvent( "cs_match_end_restart" );
|
|
if( m_bPickNewTeamsOnReset && restartEvent )
|
|
{
|
|
gameeventmanager->FireEvent( restartEvent );
|
|
}
|
|
|
|
m_bPickNewTeamsOnReset = true;
|
|
|
|
|
|
|
|
#if defined ( GAME_DLL )
|
|
// If we're taking this path, tell the server ugc manager to check for updates now and force a real change level should we need one.
|
|
if ( engine->IsDedicatedServer() )
|
|
{
|
|
DedicatedServerWorkshop().CheckIfCurrentLevelNeedsUpdate();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Don't run this code again
|
|
m_flIntermissionStartTime = 0.f;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
void CCSGameRules::CheckFreezePeriodExpired()
|
|
{
|
|
float startTime = m_fRoundStartTime;
|
|
if ( !IsFinite( startTime ) )
|
|
{
|
|
Warning( "Infinite round start time!\n" );
|
|
m_fRoundStartTime.GetForModify() = gpGlobals->curtime;
|
|
}
|
|
|
|
|
|
float flStartTime = m_fRoundStartTime;
|
|
|
|
if( IsFinite( startTime ) && ( gpGlobals->curtime < flStartTime ) )
|
|
{
|
|
|
|
if ( CSGameRules() && CSGameRules()->IsMatchWaitingForResume() )
|
|
{
|
|
m_fRoundStartTime = gpGlobals->curtime + m_iFreezeTime;
|
|
}
|
|
|
|
// TIMEOUTS
|
|
if ( m_bTerroristTimeOutActive )
|
|
{
|
|
m_fRoundStartTime = gpGlobals->curtime + m_iFreezeTime;
|
|
|
|
m_flTerroristTimeOutRemaining -= ( gpGlobals->curtime - m_flLastThinkTime );
|
|
|
|
if ( m_flTerroristTimeOutRemaining <= 0 )
|
|
{
|
|
EndTerroristTimeOut();
|
|
}
|
|
}
|
|
else if ( m_bCTTimeOutActive )
|
|
{
|
|
m_fRoundStartTime = gpGlobals->curtime + m_iFreezeTime;
|
|
|
|
m_flCTTimeOutRemaining -= ( gpGlobals->curtime - m_flLastThinkTime );
|
|
|
|
if ( m_flCTTimeOutRemaining <= 0 )
|
|
{
|
|
EndCTTimeOut();
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef CLIENT_DLL
|
|
else
|
|
{
|
|
int nTimeToStart = (int) ((flStartTime - gpGlobals->curtime) + 1.0);
|
|
if( nTimeToStart <= 3 && m_nLastFreezeEndBeep != nTimeToStart )
|
|
{
|
|
m_nLastFreezeEndBeep = nTimeToStart;
|
|
IGameEvent *pRoundStartBeepEvent = gameeventmanager->CreateEvent( "cs_round_start_beep" );
|
|
if( pRoundStartBeepEvent )
|
|
{
|
|
gameeventmanager->FireEvent( pRoundStartBeepEvent );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return; // not time yet to start round
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef CLIENT_DLL
|
|
IGameEvent *pRoundStartBeepEvent = gameeventmanager->CreateEvent( "cs_round_final_beep" );
|
|
if( pRoundStartBeepEvent )
|
|
{
|
|
gameeventmanager->FireEvent( pRoundStartBeepEvent );
|
|
}
|
|
|
|
// Mark all alive players as receiving money, they could join/reconnect at freezetime
|
|
for ( int iAlivePlayer = 1; iAlivePlayer <= MAX_PLAYERS; ++ iAlivePlayer )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iAlivePlayer ) );
|
|
|
|
if ( pPlayer ) // mark all players to have all cash available
|
|
pPlayer->m_iAccountMoneyEarnedForNextRound = 0;
|
|
|
|
if ( pPlayer && pPlayer->IsConnected() && !pPlayer->IsBot() && pPlayer->IsAlive() && !pPlayer->IsControllingBot() )
|
|
{
|
|
switch ( pPlayer->GetTeamNumber() )
|
|
{
|
|
case TEAM_TERRORIST:
|
|
case TEAM_CT:
|
|
pPlayer->MarkAsNotReceivingMoneyNextRound( true ); // allow money next round
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 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 ) );
|
|
}
|
|
|
|
// Freeze period expired: kill the flag
|
|
m_bFreezePeriod = false;
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_freeze_end" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
if ( m_match.GetRoundsPlayed() == 0 && !IsWarmupPeriod() )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_announce_match_start" );
|
|
if ( event )
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
if ( !IsPlayingGunGameTRBomb() || ( IsPlayingGunGameTRBomb() && m_match.GetPhase() != GAMEPHASE_MATCH_ENDED ) )
|
|
{
|
|
// Update the timers for all clients and play a sound
|
|
bool bCTPlayed = false;
|
|
bool bTPlayed = false;
|
|
|
|
// Don't play start round VO for terrorists in coop
|
|
if ( IsPlayingCoopMission() )
|
|
bTPlayed = true;
|
|
|
|
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->UpdateFreezetimeEndEquipmentValue();
|
|
}
|
|
|
|
//pPlayer->SyncRoundTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( mp_use_respawn_waves.GetInt() == 2 && IsPlayingCoopGuardian() )
|
|
{
|
|
int nTeam = IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
|
|
//int nOtherTeam = IsHostageRescueMap() ? TEAM_CT : TEAM_TERRORIST;
|
|
|
|
// if ( nTeam == TEAM_CT )
|
|
// m_bCTCantBuy = true;
|
|
// else
|
|
// m_bTCantBuy = true;
|
|
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->GetTeamNumber() == nTeam && !IsWarmupPeriod() )
|
|
{
|
|
CRecipientFilter filter;
|
|
filter.AddRecipient( pPlayer );
|
|
|
|
const char *szTeam = (nTeam == TEAM_CT) ? "CT" : "T";
|
|
char szNotice[512];
|
|
Q_snprintf( szNotice, sizeof( szNotice ), "#SFUI_Notice_NewWaveBegun_%s0", szTeam );
|
|
|
|
char szWave[32];
|
|
Q_snprintf( szWave, sizeof( szWave ), "%d", ( int )m_nGuardianModeWaveNumber );
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTCENTER, szNotice, szWave );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CCSGameRules::CheckRoundTimeExpired()
|
|
{
|
|
if ( mp_ignore_round_win_conditions.GetBool() || IsWarmupPeriod() )
|
|
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 ( IsPlayingGunGameDeathmatch() )
|
|
{
|
|
// TODO: make this a shared function so playercount runs the same code
|
|
CCSPlayer *pWinner = NULL;
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
if ( pWinner == NULL )
|
|
pWinner = pPlayer;
|
|
|
|
if ( pWinner != pPlayer )
|
|
{
|
|
if ( pWinner->GetScore() > pPlayer->GetScore() )
|
|
continue;
|
|
else if ( pWinner->GetScore() < pPlayer->GetScore() )
|
|
pWinner = pPlayer;
|
|
else
|
|
pWinner = (pWinner->entindex() > pPlayer->entindex()) ? pWinner : pPlayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pWinner )
|
|
{
|
|
if ( pWinner->GetTeamNumber() == TEAM_CT )
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win );
|
|
else
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
}
|
|
else
|
|
{
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw );
|
|
}
|
|
}
|
|
else if ( IsPlayingCoopMission() )
|
|
{
|
|
// When playing coop mission Terrorists win if timer runs out
|
|
bool bCTsFailedToDeploy = m_coopPlayersInDeploymentZone;
|
|
// However if CTs even failed to deploy in time kick them from the server
|
|
m_match.AddTerroristWins( 1 );
|
|
if ( bCTsFailedToDeploy )
|
|
{
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_CT ) )
|
|
pTeam->MarkSurrendered();
|
|
|
|
CReliableBroadcastRecipientFilter filter;
|
|
UTIL_ClientPrintFilter(filter, HUD_PRINTCENTER, "#SFUIHUD_InfoPanel_Coop_DeployMissionBust" );
|
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "#SFUIHUD_InfoPanel_Coop_DeployMissionBust" );
|
|
}
|
|
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
|
|
if ( bCTsFailedToDeploy )
|
|
{ // Mission is a bust
|
|
m_match.SetPhase( GAMEPHASE_MATCH_ENDED );
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + 1.5;
|
|
GoToIntermission();
|
|
}
|
|
}
|
|
else if ( mp_default_team_winner_no_objective.GetInt() != -1 )
|
|
{
|
|
int nTeam = mp_default_team_winner_no_objective.GetInt();
|
|
|
|
if ( nTeam == TEAM_CT )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
if ( IsPlayingCoopGuardian() && !IsHostageRescueMap() )
|
|
{
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST ) )
|
|
pTeam->MarkSurrendered();
|
|
}
|
|
else
|
|
{
|
|
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound( TEAM_TERRORIST );
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::WIN_BY_TIME_RUNNING_OUT_BOMB );
|
|
}
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win );
|
|
}
|
|
else if ( nTeam == TEAM_TERRORIST )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
if ( IsPlayingCoopGuardian() && IsHostageRescueMap() )
|
|
{
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_CT ) )
|
|
pTeam->MarkSurrendered();
|
|
}
|
|
else
|
|
{
|
|
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound( TEAM_CT );
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::WIN_BY_TIME_RUNNING_OUT_HOSTAGE );
|
|
}
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win );
|
|
}
|
|
else
|
|
{
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw );
|
|
}
|
|
}
|
|
else 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_match.AddCTWins( 1 );
|
|
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_TERRORIST);
|
|
AddTeamAccount( TEAM_CT, TeamCashAward::WIN_BY_TIME_RUNNING_OUT_BOMB );
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Target_Saved );
|
|
}
|
|
}
|
|
else if ( m_bMapHasRescueZone )
|
|
{
|
|
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_CT);
|
|
m_match.AddTerroristWins( 1 );
|
|
AddTeamAccount( TEAM_TERRORIST, TeamCashAward::WIN_BY_TIME_RUNNING_OUT_HOSTAGE );
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Hostages_Not_Rescued );
|
|
|
|
}
|
|
else if ( m_bMapHasEscapeZone )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Not_Escaped );
|
|
}
|
|
//If there is no scenario-specific winner when the time runs out, just declare a draw
|
|
else
|
|
{
|
|
TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw );
|
|
}
|
|
}
|
|
|
|
bool CCSGameRules::ShouldGunGameSpawnBomb( void )
|
|
{
|
|
return ( m_bGunGameRespawnWithBomb && ( m_fGunGameBombRespawnTimer <= gpGlobals->curtime ) );
|
|
}
|
|
|
|
void CCSGameRules::SetGunGameSpawnBomb( bool allow )
|
|
{
|
|
m_bGunGameRespawnWithBomb = allow;
|
|
m_fGunGameBombRespawnTimer = gpGlobals->curtime + mp_ggtr_bomb_respawn_delay.GetFloat();
|
|
}
|
|
|
|
void CCSGameRules::RewardMatchEndDrops( bool bAbortedMatch )
|
|
{
|
|
}
|
|
|
|
void CCSGameRules::GoToIntermission( bool bAbortedMatch )
|
|
{
|
|
Msg( "Going to intermission...\n" );
|
|
|
|
bool bAnnounceNextMap = true;
|
|
bool bDoGenericRewardMatchEndDrops = true;
|
|
|
|
// Do old style match end drops (community and official non-competitive)
|
|
if ( bDoGenericRewardMatchEndDrops )
|
|
{
|
|
RewardMatchEndDrops( bAbortedMatch );
|
|
}
|
|
else
|
|
{
|
|
ClearItemsDroppedDuringMatch();
|
|
}
|
|
m_bIsDroppingItems = true; // Always wait in case items drop
|
|
|
|
// generate the map list that players will be voting on
|
|
CreateEndMatchMapGroupVoteOptions();
|
|
|
|
m_match.SetPhase( GAMEPHASE_MATCH_ENDED );
|
|
|
|
m_bHasMatchStarted = false;
|
|
|
|
IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" );
|
|
if ( winEvent )
|
|
{
|
|
gameeventmanager->FireEvent( winEvent );
|
|
}
|
|
|
|
BaseClass::GoToIntermission();
|
|
|
|
//
|
|
// Tell everyone what the next map is
|
|
//
|
|
bool bNextMapAlreadySet = false;
|
|
char szNextMap[ MAX_PATH ] = { 0 };
|
|
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
|
|
{
|
|
bNextMapAlreadySet = true;
|
|
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
|
|
}
|
|
else
|
|
{
|
|
GetNextLevelName( szNextMap, sizeof( szNextMap ) );
|
|
}
|
|
|
|
// if we're running map group, let players vote to pick the next map at the end of the match
|
|
if ( IsEndMatchVotingForNextMap() && !bNextMapAlreadySet && GetCMMItemDropRevealEndTime() < gpGlobals->curtime )
|
|
{
|
|
bAnnounceNextMap = !CheckSetVoteTime();
|
|
}
|
|
else if ( !m_bIsDroppingItems )
|
|
{
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_VoteAllDone;
|
|
}
|
|
|
|
// In queued matchmaking or during a "season" map group, don't message the next map
|
|
if ( IsQueuedMatchmaking() || bAnnounceNextMap == false )
|
|
{
|
|
szNextMap[ 0 ] = 0;
|
|
}
|
|
|
|
//Clear various states from all players and freeze them in place
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->Unblind();
|
|
pPlayer->AddFlag( FL_FROZEN );
|
|
pPlayer->SetPendingTeamNum( TEAM_UNASSIGNED );
|
|
UpdateMatchStats( pPlayer, m_match.GetWinningTeam() );
|
|
|
|
extern ConVar nextmap_print_enabled;
|
|
if ( szNextMap[ 0 ] && nextmap_print_enabled.GetBool() )
|
|
{
|
|
if ( IsGameConsole() || engine->IsDedicatedServerForXbox() || engine->IsDedicatedServerForPS3() )
|
|
{
|
|
// we know all the map names on console so we can safely assume we have a token for all maps
|
|
const int nMaxLength = MAX_MAP_NAME + 10 + 1;
|
|
char szToken[ nMaxLength ];
|
|
CreateFriendlyMapNameToken( szNextMap, szToken, nMaxLength );
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", szToken );
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", V_GetFileName( szNextMap ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// freeze players while in intermission
|
|
m_bFreezePeriod = true;
|
|
|
|
// When not queued matchmaking that is driven by rematch endmatch vote machine reveal all ranks now
|
|
if ( !IsQueuedMatchmaking() )
|
|
{
|
|
CReliableBroadcastRecipientFilter filter;
|
|
CCSUsrMsg_ServerRankRevealAll msg;
|
|
SendUserMessage( filter, CS_UM_ServerRankRevealAll, msg );
|
|
}
|
|
}
|
|
|
|
bool CCSGameRules::CheckSetVoteTime()
|
|
{
|
|
int numMaps = 0;
|
|
if ( g_pGameTypes )
|
|
{
|
|
const char* mapGroupName = gpGlobals->mapGroupName.ToCStr();
|
|
const CUtlStringList* mapsInGroup = g_pGameTypes->GetMapGroupMapList( mapGroupName );
|
|
if ( mapsInGroup )
|
|
numMaps = mapsInGroup->Count();
|
|
}
|
|
|
|
if ( numMaps > 1 )
|
|
{
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_VoteInProgress;
|
|
|
|
m_nEndMatchTiedVotes.RemoveAll();
|
|
|
|
// artificially set the m_flIntermissionStartTime
|
|
if ( m_flIntermissionStartTime &&
|
|
( gpGlobals->curtime + mp_endmatch_votenextleveltime.GetFloat() > m_flIntermissionStartTime + GetIntermissionDuration() ) )
|
|
{
|
|
m_flIntermissionStartTime = gpGlobals->curtime + mp_endmatch_votenextleveltime.GetFloat() - GetIntermissionDuration();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
m_eEndMatchMapVoteState = k_EEndMatchMapVoteState_VoteAllDone;
|
|
return false;
|
|
}
|
|
|
|
void CCSGameRules::GoToMatchRestartIntermission()
|
|
{
|
|
|
|
//We use our own intermission steps here to bypass the match win panel (AKA scoreboard)
|
|
BaseClass::GoToIntermission();
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->Unblind();
|
|
|
|
// set all players to FL_FROZEN
|
|
pPlayer->AddFlag( FL_FROZEN );
|
|
}
|
|
}
|
|
|
|
// freeze players while in intermission
|
|
m_bFreezePeriod = true;
|
|
}
|
|
|
|
bool CCSGameRules::GameModeSupportsHealthBuffer( void )
|
|
{
|
|
|
|
return false;
|
|
}
|
|
|
|
void CCSGameRules::UpdateMatchStats( CCSPlayer* pPlayer, int winnerIndex )
|
|
{
|
|
|
|
if( pPlayer->GetTeamNumber() == winnerIndex)
|
|
{
|
|
CCS_GameStats.IncrementStat( pPlayer, CSSTAT_MATCHES_WON, 1, false, true );
|
|
if( IsPlayingGunGame() )
|
|
{
|
|
|
|
if( ( IsPlayingGunGameProgressive() && pPlayer->MadeFinalGunGameProgressiveKill() ) ||
|
|
( IsPlayingGunGameTRBomb() ) )
|
|
{
|
|
CCS_GameStats.IncrementStat(pPlayer,CSSTAT_GUN_GAME_MATCHES_WON,1);
|
|
}
|
|
|
|
if( IsPlayingGunGameProgressive() )
|
|
{
|
|
CCS_GameStats.IncrementStat(pPlayer,CSSTAT_GUN_GAME_PROGRESSIVE_MATCHES_WON,1);
|
|
|
|
// check for a rampage: See if we made it thru gun game progressive without dying once
|
|
if( pPlayer->WasKilledThisRound() == false && pPlayer->MadeFinalGunGameProgressiveKill() )
|
|
{
|
|
pPlayer->AwardAchievement(CSGunGameProgressiveRampage);
|
|
}
|
|
|
|
// see if we won a progressive match without reloading
|
|
if( !pPlayer->HasReloaded() )
|
|
{
|
|
pPlayer->AwardAchievement(CSGunGameConservationist);
|
|
}
|
|
}
|
|
else if( IsPlayingGunGameTRBomb())
|
|
{
|
|
CCS_GameStats.IncrementStat(pPlayer,CSSTAT_GUN_GAME_TRBOMB_MATCHES_WON,1);
|
|
}
|
|
}
|
|
|
|
int mapIndex = GetCSLevelIndex(gpGlobals->mapname.ToCStr());
|
|
if ( mapIndex != -1 )
|
|
{
|
|
CSStatType_t matchStatId = MapName_StatId_Table[mapIndex].matchesWonId;
|
|
// look up our map information and increment the matches won stat. Rounds and other stats are done elsewhere
|
|
if(matchStatId != CSSTAT_UNDEFINED)
|
|
CCS_GameStats.IncrementStat( pPlayer,matchStatId, 1, false, true );
|
|
}
|
|
|
|
}
|
|
else if( winnerIndex == WINNER_DRAW )
|
|
{
|
|
CCS_GameStats.IncrementStat( pPlayer, CSSTAT_MATCHES_DRAW, 1, false, true );
|
|
}
|
|
CCS_GameStats.IncrementStat( pPlayer, CSSTAT_MATCHES_PLAYED, 1, false, true );
|
|
|
|
if( IsPlayingGunGame() )
|
|
{
|
|
CCS_GameStats.IncrementStat( pPlayer, CSSTAT_GUN_GAME_MATCHES_PLAYED, 1, false, true );
|
|
}
|
|
|
|
// CSN-8618 and others - Make sure these get sent before stats are reset.
|
|
// End rounds stats are sent BEFORE this call, and the match ends before the next send happens
|
|
// (resetting what we're recording above). Send these now so they don't get lost.
|
|
CCS_GameStats.SendStatsToPlayer( pPlayer, CSSTAT_PRIORITY_ENDROUND );
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// Contribution score helper functions
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
inline float FalloffWeight( float fDistance, float fRangeInner, float fRangeOuter )
|
|
{
|
|
if ( fDistance > fRangeInner )
|
|
return (fRangeOuter - fDistance) / (fRangeOuter - fRangeInner);
|
|
else
|
|
return 1.0f;
|
|
}
|
|
|
|
// splits point amongst all players in zone
|
|
void CCSGameRules::SplitScoreAmongPlayersInZone( int iPoints, int iTeam, CCSPlayer* pExcludePlayer, uint iPlace )
|
|
{
|
|
float fWeights[MAX_PLAYERS+1];
|
|
memset( fWeights, 0, sizeof( fWeights ) );
|
|
float fWeightSum = 0.0f;
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if ( pPlayer == pExcludePlayer )
|
|
continue;
|
|
|
|
if ( pPlayer->GetTeamNumber() != iTeam )
|
|
continue;
|
|
|
|
if ( pPlayer->GetLastKnownArea() && pPlayer->GetLastKnownArea()->GetPlace() == iPlace )
|
|
{
|
|
fWeightSum += 1.0f;
|
|
fWeights[i] = 1.0f;
|
|
}
|
|
}
|
|
|
|
if ( fWeightSum == 0.0f )
|
|
return;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
if ( fWeights[i] > 0.0f )
|
|
{
|
|
CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
ASSERT( pPlayer );
|
|
int score = RoundFloatToInt(iPoints * fWeights[i] / fWeightSum );
|
|
pPlayer->AddScore( score );
|
|
pPlayer->AddRoundProximityScore( score );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::SplitScoreAmongPlayersInRange( int iPoints, int iTeam, CCSPlayer* pExcludePlayer, const Vector& center, float fRangeInner, float fRangeOuter )
|
|
{
|
|
float fMaxRangeSquared = fRangeOuter * fRangeOuter;
|
|
float fWeights[MAX_PLAYERS+1];
|
|
memset( fWeights, 0, sizeof( fWeights ) );
|
|
float fWeightSum = 0.0f;
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if ( pPlayer == pExcludePlayer )
|
|
continue;
|
|
|
|
if ( pPlayer->GetTeamNumber() != iTeam )
|
|
continue;
|
|
|
|
if ( center.DistToSqr( pPlayer->GetAbsOrigin() ) <= fMaxRangeSquared )
|
|
{
|
|
float fWeight = FalloffWeight( center.DistTo( pPlayer->GetAbsOrigin()), fRangeInner, fRangeOuter );
|
|
fWeightSum += fWeight;
|
|
fWeights[i] = fWeight;
|
|
}
|
|
}
|
|
|
|
if ( fWeightSum == 0.0f )
|
|
return;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
if ( fWeights[i] > 0.0f )
|
|
{
|
|
CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
int score = RoundFloatToInt( iPoints * fWeights[i] / fWeightSum );
|
|
pPlayer->AddScore( score );
|
|
pPlayer->AddRoundProximityScore( score );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScorePlayerKill( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
if ( IsPlayingGunGameDeathmatch() && pPlayer->GetActiveCSWeapon() )
|
|
{
|
|
CEconItemView* pItem = pPlayer->GetActiveCSWeapon()->GetEconItemView();
|
|
CSWeaponID wepID = ( pItem && pItem->IsValid() ) ? (CSWeaponID)(pItem->GetItemIndex()) : pPlayer->GetActiveCSWeapon()->GetCSWeaponID();
|
|
int iWepSlot = ( pItem && pItem->GetItemDefinition() ) ? pItem->GetItemDefinition()->GetDefaultLoadoutSlot() : LOADOUT_POSITION_INVALID;
|
|
|
|
int nScore = GetWeaponScoreForDeathmatch( iWepSlot );
|
|
if ( pPlayer->AddDeathmatchKillScore( nScore, wepID, iWepSlot ) <= 0 )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_kill.GetInt() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_kill.GetInt() );
|
|
pPlayer->AddScore( RoundFloatToInt( score_kill_enemy_bonus.GetFloat() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScorePlayerAssist( CCSPlayer* pPlayer, CCSPlayer* pCSVictim )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
if ( IsPlayingGunGameDeathmatch() && pPlayer->GetActiveCSWeapon() )
|
|
{
|
|
CEconItemView* pItem = pPlayer->GetActiveCSWeapon()->GetEconItemView();
|
|
CSWeaponID wepID = ( pItem && pItem->IsValid() ) ? (CSWeaponID)(pItem->GetItemIndex()) : pPlayer->GetActiveCSWeapon()->GetCSWeaponID();
|
|
//int nScore = GetWeaponScoreForDeathmatch( wepID );
|
|
// we don't store what weapon the player did the assist damage with and we can't guarantee the player has a weapon
|
|
// when the assist is awarded, so we just give them and average of half the points awarded
|
|
int nScore = 6;
|
|
pPlayer->AddDeathmatchKillScore( nScore, wepID, pItem->GetItemDefinition()->GetDefaultLoadoutSlot(), true, pCSVictim->GetPlayerName() );
|
|
pPlayer->IncrementAssistsCount( 1 );
|
|
}
|
|
else if ( IPointsForKill( pPlayer, pCSVictim ) > 0 ) // this ensures that only assists on enemies are recorded, but "assists" for teammate kills are not
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_assist.GetInt() );
|
|
pPlayer->IncrementAssistsCount( 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScorePlayerObjectiveKill( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_objective_kill.GetInt() );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScorePlayerTeamKill( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_team_kill.GetInt( ) );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScorePlayerSuicide( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_suicide.GetInt( ) );
|
|
|
|
if ( !m_bLoadingRoundBackupData )
|
|
pPlayer->ProcessSuicideAsKillReward();
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScorePlayerDamage( CCSPlayer* pPlayer, float fDamage )
|
|
{
|
|
pPlayer->AddScore( RoundFloatToInt( fDamage * score_damage.GetFloat() ) );
|
|
|
|
Place iPlace = pPlayer->GetLastKnownArea() ? pPlayer->GetLastKnownArea()->GetPlace() : UNDEFINED_PLACE;
|
|
if ( iPlace != UNDEFINED_PLACE )
|
|
SplitScoreAmongPlayersInZone( RoundFloatToInt( fDamage * score_team_damage_bonus.GetFloat() ), pPlayer->GetTeamNumber(), pPlayer, iPlace );
|
|
|
|
// award bonus for being near planted bomb
|
|
{
|
|
const float fRadiusInner = score_planted_bomb_proximity_damage_radius_inner.GetFloat();
|
|
const float fRadiusOuter = score_planted_bomb_proximity_damage_radius_outer.GetFloat();
|
|
const float fRadiusOuterSquared = fRadiusOuter * fRadiusOuter;
|
|
FOR_EACH_VEC( g_PlantedC4s, iBomb )
|
|
{
|
|
CPlantedC4 *pC4 = g_PlantedC4s[iBomb];
|
|
if ( pC4 && pC4->IsBombActive() )
|
|
{
|
|
Vector bombPos = pC4->GetAbsOrigin();
|
|
Vector playerToBomb = pPlayer->GetAbsOrigin() - bombPos;
|
|
if ( playerToBomb.LengthSqr() < fRadiusOuterSquared )
|
|
{
|
|
float fWeight = FalloffWeight( playerToBomb.Length(), fRadiusInner, fRadiusOuter );
|
|
int score = RoundFloatToInt( fWeight * score_planted_bomb_proximity_damage_bonus.GetFloat() );
|
|
pPlayer->AddScore( score );
|
|
pPlayer->AddRoundProximityScore( score );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// bonus for being near hostage
|
|
const float fRadiusInner = score_hostage_proximity_damage_radius_inner.GetFloat();
|
|
const float fRadiusOuter = score_hostage_proximity_damage_radius_outer.GetFloat();
|
|
float fClosestSquared = FLT_MAX;
|
|
FOR_EACH_VEC( g_Hostages, iHostage )
|
|
{
|
|
CHostage* pHostage = g_Hostages[iHostage];
|
|
if ( pHostage->m_iHealth > 0 && !pHostage->IsRescued() )
|
|
{
|
|
float fDistanceSquared = pHostage->GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
|
|
if ( fDistanceSquared < fClosestSquared )
|
|
{
|
|
fClosestSquared = fDistanceSquared;
|
|
}
|
|
}
|
|
}
|
|
if ( fClosestSquared < fRadiusOuter * fRadiusOuter )
|
|
{
|
|
float fWeight = FalloffWeight( sqrtf( fClosestSquared ), fRadiusInner, fRadiusOuter );
|
|
int score = RoundFloatToInt( fDamage * fWeight * score_hostage_proximity_damage_bonus.GetFloat() );
|
|
pPlayer->AddScore( score );
|
|
pPlayer->AddRoundProximityScore( score );
|
|
}
|
|
|
|
// damage near dropped bomb bonus
|
|
{
|
|
const float fRadiusInner = score_dropped_bomb_proximity_damage_bonus_radius_inner.GetFloat();
|
|
const float fRadiusOuter = score_dropped_bomb_proximity_damage_bonus_radius_outer.GetFloat();
|
|
CBaseEntity* pC4 = gEntList.FindEntityByClassnameNearest( "weapon_c4", pPlayer->GetAbsOrigin(), fRadiusOuter );
|
|
if ( pC4 && pC4->GetOwnerEntity() == NULL )
|
|
{
|
|
float fWeight = FalloffWeight( pC4->GetAbsOrigin().DistTo( pPlayer->GetAbsOrigin()), fRadiusInner, fRadiusOuter );
|
|
int score = RoundFloatToInt( fDamage * fWeight * score_dropped_bomb_proximity_damage_bonus.GetFloat() );
|
|
pPlayer->AddScore( score );
|
|
pPlayer->AddRoundProximityScore( score );
|
|
}
|
|
}
|
|
|
|
// damage near dropped defuser
|
|
{
|
|
const float fRadiusInner = score_dropped_defuser_proximity_damage_radius_inner.GetFloat();
|
|
const float fRadiusOuter = score_dropped_defuser_proximity_damage_radius_outer.GetFloat();
|
|
CBaseEntity* pDefuser = gEntList.FindEntityByClassnameNearest( "item_defuser", pPlayer->GetAbsOrigin(), fRadiusOuter );
|
|
if ( pDefuser && pDefuser->GetOwnerEntity() == NULL )
|
|
{
|
|
float fWeight = FalloffWeight( pDefuser->GetAbsOrigin().DistTo( pPlayer->GetAbsOrigin() ), fRadiusInner, fRadiusOuter );
|
|
int score = RoundFloatToInt( fDamage * fWeight * score_dropped_defuser_proximity_damage_bonus.GetFloat() );
|
|
pPlayer->AddScore( score );
|
|
pPlayer->AddRoundProximityScore( score );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreBombPlant( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_bomb_planted.GetInt() );
|
|
}
|
|
|
|
CPlantedC4 *pC4 = g_PlantedC4s[0];
|
|
ASSERT( pC4 != NULL );
|
|
if ( pC4 )
|
|
{
|
|
SplitScoreAmongPlayersInRange( score_bomb_plant_bonus.GetInt(), TEAM_TERRORIST, NULL, pC4->GetAbsOrigin(),
|
|
score_bomb_plant_radius_inner.GetFloat(), score_bomb_plant_radius_outer.GetFloat() );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreBombExploded( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_bomb_exploded.GetInt() );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreBombDefuse( CCSPlayer* pPlayer, bool bMajorEvent )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( bMajorEvent ? contributionscore_bomb_defuse_major.GetInt() : contributionscore_bomb_defuse_minor.GetInt() );
|
|
}
|
|
|
|
m_bBombDefused = true;
|
|
|
|
CPlantedC4 *pC4 = g_PlantedC4s[0];
|
|
ASSERT( pC4 != NULL );
|
|
if ( pC4 && bMajorEvent )
|
|
{
|
|
SplitScoreAmongPlayersInRange( score_bomb_defuse_bonus.GetInt(), TEAM_CT, NULL, pC4->GetAbsOrigin(),
|
|
score_bomb_defuse_radius_inner.GetFloat(), score_bomb_defuse_radius_outer.GetFloat() );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreHostageRescue( CCSPlayer* pPlayer, CHostage* pHostage, bool bMajorEvent )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( bMajorEvent ? contributionscore_hostage_rescue_major.GetInt() : contributionscore_hostage_rescue_minor.GetInt() );
|
|
}
|
|
|
|
ASSERT( pHostage != NULL );
|
|
if ( pHostage && bMajorEvent )
|
|
{
|
|
SplitScoreAmongPlayersInRange( score_hostage_rescue_bonus.GetInt(), TEAM_CT, NULL, pHostage->GetAbsOrigin(),
|
|
score_hostage_rescue_radius_inner.GetFloat(), score_hostage_rescue_radius_outer.GetFloat() );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreHostageKilled( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddContributionScore( contributionscore_hostage_kill.GetInt() );
|
|
}
|
|
}
|
|
|
|
|
|
void CCSGameRules::ScoreHostageDamage( CCSPlayer* pPlayer, float fDamage )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddScore( -RoundFloatToInt( fDamage * score_hostage_damage_penalty.GetInt() ) );
|
|
}
|
|
}
|
|
|
|
|
|
void CCSGameRules::ScoreFriendlyFire( CCSPlayer* pPlayer, float fDamage )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddScore( -RoundFloatToInt( fDamage * score_ff_damage.GetFloat() ) );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreBlindEnemy( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddScore( score_blind_enemy_bonus.GetInt() );
|
|
|
|
if ( ShouldRecordMatchStats() )
|
|
{
|
|
// match stats for blinding enemy
|
|
pPlayer->m_iMatchStats_EnemiesFlashed.GetForModify( GetRoundsPlayed( ) ) ++; // record in MatchStats'
|
|
|
|
// Keep track in QMM data
|
|
if ( pPlayer->GetHumanPlayerAccountID() )
|
|
{
|
|
if ( CCSGameRules::CQMMPlayerData_t *pQMM = QueuedMatchmakingPlayersDataFind( pPlayer->GetHumanPlayerAccountID() ) )
|
|
{
|
|
pQMM->m_iMatchStats_EnemiesFlashed[ GetRoundsPlayed( ) ] = pPlayer->m_iMatchStats_EnemiesFlashed.Get( GetRoundsPlayed( ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ScoreBlindFriendly( CCSPlayer* pPlayer )
|
|
{
|
|
ASSERT( pPlayer != NULL );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddScore( -score_blind_friendly_penalty.GetInt() );
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// End Contribution score functions
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
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.Get() ) );
|
|
|
|
PrintToConsole( player, str.sprintf( "first connected: %d\n", m_bFirstConnected ) );
|
|
PrintToConsole( player, str.sprintf( "intermission start time: %f\n", m_flIntermissionStartTime ) );
|
|
PrintToConsole( player, str.sprintf( "intermission duration: %f\n", GetIntermissionDuration() ) );
|
|
PrintToConsole( player, str.sprintf( "freeze period: %d\n", m_bFreezePeriod.Get() ) );
|
|
PrintToConsole( player, str.sprintf( "round restart time: %f\n", m_flRestartRoundTime.Get() ) );
|
|
PrintToConsole( player, str.sprintf( "game start time: %f\n", m_flGameStartTime.Get() ) );
|
|
PrintToConsole( player, str.sprintf( "m_fMatchStartTime: %f\n", m_fMatchStartTime.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()))
|
|
{
|
|
// Exception here: only here and not inside "MarkAsNotReceivingMoneyNextRound"
|
|
// to not affect team-wide money management, round backups, etc.
|
|
|
|
// When a player is alive and controlling a bot then the monetary punishment should go
|
|
// to the bot being controlled
|
|
if ( player->IsControllingBot() )
|
|
{
|
|
if ( CCSPlayer* controlledBotPlayer = player->GetControlledBot() )
|
|
player = controlledBotPlayer;
|
|
}
|
|
|
|
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
|
|
|
|
m_iSpawnPointCount_Terrorist = 0;
|
|
m_iSpawnPointCount_CT = 0;
|
|
|
|
m_iMaxNumTerrorists = 0;
|
|
m_iMaxNumCTs = 0;
|
|
|
|
m_flCoopRespawnAndHealTime = -1;
|
|
|
|
const char * szMapName = STRING( gpGlobals->mapname );
|
|
int nNumSlots = 2;
|
|
uint32 dwRichPresenceContext = 0xFFFF;
|
|
if ( szMapName )
|
|
{
|
|
g_pGameTypes->GetMapInfo( szMapName, dwRichPresenceContext );
|
|
}
|
|
|
|
int iGameType = g_pGameTypes->GetCurrentGameType();
|
|
int iGameMode = g_pGameTypes->GetCurrentGameMode();
|
|
|
|
nNumSlots = g_pGameTypes->GetMaxPlayersForTypeAndMode( iGameType, iGameMode );
|
|
|
|
// for the training map, our max is 1 CT and 0 T's, diving 1 in half doesn't work for us in this case, so make sure our min is 1
|
|
m_iMaxNumTerrorists = MAX( nNumSlots / 2, 1 );
|
|
m_iMaxNumCTs = MAX( nNumSlots / 2, 1 );
|
|
|
|
m_iSpectatorSlotCount = mp_spectators_max.GetInt();
|
|
|
|
|
|
m_nGuardianModeWaveNumber = 1;
|
|
m_nGuardianModeSpecialKillsRemaining = mp_guardian_special_kills_needed.GetInt();
|
|
|
|
// create the spawn point lists here
|
|
GenerateSpawnPointListsFirstTime();
|
|
|
|
// Is this a logo map?
|
|
if ( gEntList.FindEntityByClassname( NULL, "info_player_logo" ) )
|
|
m_bLogoMap = true;
|
|
|
|
m_bLevelInitialized = true;
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::DoCoopSpawnAndNavInit( void )
|
|
{
|
|
// TODO: here is where we should pick the spawn points we will use for coop and delete the rest
|
|
|
|
if ( m_vecMainCTSpawnPos == Vector( 0, 0, 0 ) )
|
|
{
|
|
CBaseEntity* ent = NULL;
|
|
// we need to find at least one CT spawn point to figure out where the "start" of the map is
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) )
|
|
{
|
|
SpawnPoint* pSpawnPoint = assert_cast< SpawnPoint* >( ent );
|
|
if ( pSpawnPoint )
|
|
{
|
|
m_vecMainCTSpawnPos = pSpawnPoint->GetAbsOrigin();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( TheNavMesh && !TheNavMesh->IsLoaded() && !TheNavMesh->IsAnalyzed() && !TheNavMesh->IsGenerating() )
|
|
{
|
|
// If there isn't a Navigation Mesh in memory, create one
|
|
// we do this here because we need to generate the nav each time we create a new map
|
|
// and generated it when a bot join is too late
|
|
ConVarRef nav_generate_fast_for_tilegen( "nav_generate_fast_for_tilegen" );
|
|
if ( nav_generate_fast_for_tilegen.IsValid() )
|
|
nav_generate_fast_for_tilegen.SetValue( 1 );
|
|
|
|
TheNavMesh->BeginGeneration();
|
|
}
|
|
else
|
|
{
|
|
ConVarRef bot_quota( "bot_quota" );
|
|
bot_quota.SetValue( 20 );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::AddSpawnPointToMasterList( SpawnPoint* pSpawnPoint )
|
|
{
|
|
//if classname == T
|
|
if ( FClassnameIs( pSpawnPoint, "info_player_terrorist" ) ||
|
|
FClassnameIs( pSpawnPoint, "info_enemy_terrorist_spawn" ) ||
|
|
FClassnameIs( pSpawnPoint, "info_armsrace_terrorist" ) )
|
|
{
|
|
// check to make sure it isn't already in the list
|
|
if ( m_TerroristSpawnPointsMasterList.Find( pSpawnPoint ) != m_TerroristSpawnPointsMasterList.InvalidIndex() )
|
|
{
|
|
AssertMsg( false, "AddSpawnPointToMasterList tried to add a spawn point to the list, but it already exists in the list!" );
|
|
return;
|
|
}
|
|
|
|
m_TerroristSpawnPointsMasterList.AddToTail( pSpawnPoint );
|
|
}
|
|
else if ( FClassnameIs( pSpawnPoint, "info_player_counterterrorist" ) ||
|
|
FClassnameIs( pSpawnPoint, "info_armsrace_counterterrorist" ) )
|
|
{
|
|
if ( m_CTSpawnPointsMasterList.Find( pSpawnPoint ) != m_CTSpawnPointsMasterList.InvalidIndex() )
|
|
{
|
|
AssertMsg( false, "AddSpawnPointToMasterList tried to add a spawn point to the list, but it already exists in the list!" );
|
|
return;
|
|
}
|
|
|
|
m_CTSpawnPointsMasterList.AddToTail( pSpawnPoint );
|
|
}
|
|
else if ( FClassnameIs( pSpawnPoint, "info_deathmatch_spawn" ) )
|
|
{
|
|
// No team specific spawns in this mode
|
|
}
|
|
else
|
|
{
|
|
// doesn't match any classes we are aware of!
|
|
AssertMsg( false, "AddSpawnPointToMasterList is looking to adda class to the master spawn list, but it doesn't recognize the class type!" );
|
|
}
|
|
|
|
RefreshCurrentSpawnPointLists();
|
|
}
|
|
|
|
void CCSGameRules::GenerateSpawnPointListsFirstTime( void )
|
|
{
|
|
//CUtlVector< SpawnPoint* > m_CTSpawnPointsMasterList; // The master list of CT spawn points (contains all points whether enabled or disabled)
|
|
//CUtlVector< SpawnPoint* > m_TerroristSpawnPointsMasterList; // The master list of Terrorist spawn points (contains all points whether enabled or disabled)
|
|
|
|
// Clear out existing spawn point lists
|
|
m_TerroristSpawnPointsMasterList.RemoveAll();
|
|
m_CTSpawnPointsMasterList.RemoveAll();
|
|
|
|
const char* szTSpawnEntName = "info_player_terrorist";
|
|
CBaseEntity* ent = NULL;
|
|
if ( IsPlayingCoopMission() )
|
|
szTSpawnEntName = "info_enemy_terrorist_spawn";
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, szTSpawnEntName ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) )
|
|
{
|
|
SpawnPoint* pSpawnPoint = assert_cast< SpawnPoint* >( ent );
|
|
if ( pSpawnPoint )
|
|
{
|
|
// Store off the terrorist spawn point
|
|
m_TerroristSpawnPointsMasterList.AddToTail( pSpawnPoint );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Warning("Invalid terrorist spawnpoint at (%.1f,%.1f,%.1f)\n",
|
|
ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[1],ent->GetAbsOrigin()[2] );
|
|
}
|
|
}
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) )
|
|
{
|
|
SpawnPoint* pSpawnPoint = assert_cast< SpawnPoint* >( ent );
|
|
if ( pSpawnPoint )
|
|
{
|
|
// Store off the CT spawn point
|
|
m_CTSpawnPointsMasterList.AddToTail( pSpawnPoint );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Warning("Invalid counterterrorist spawnpoint at (%.1f,%.1f,%.1f)\n",
|
|
ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[1],ent->GetAbsOrigin()[2] );
|
|
}
|
|
}
|
|
|
|
ent = NULL;
|
|
// if we're playing armsrace, add the armsrace spawns to the list as well
|
|
if ( CSGameRules()->IsPlayingGunGameProgressive() )
|
|
{
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_armsrace_terrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) )
|
|
{
|
|
SpawnPoint* pSpawnPoint = assert_cast< SpawnPoint* >( ent );
|
|
if ( pSpawnPoint )
|
|
{
|
|
pSpawnPoint->m_nType = SpawnPoint::ArmsRace;
|
|
// Store off the terrorist spawn point
|
|
m_TerroristSpawnPointsMasterList.AddToTail( pSpawnPoint );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Warning( "Invalid terrorist spawnpoint at (%.1f,%.1f,%.1f)\n",
|
|
ent->GetAbsOrigin()[0], ent->GetAbsOrigin()[1], ent->GetAbsOrigin()[2] );
|
|
}
|
|
}
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_armsrace_counterterrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) )
|
|
{
|
|
SpawnPoint* pSpawnPoint = assert_cast< SpawnPoint* >( ent );
|
|
if ( pSpawnPoint )
|
|
{
|
|
pSpawnPoint->m_nType = SpawnPoint::ArmsRace;
|
|
// Store off the CT spawn point
|
|
m_CTSpawnPointsMasterList.AddToTail( pSpawnPoint );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Warning( "Invalid counterterrorist spawnpoint at (%.1f,%.1f,%.1f)\n",
|
|
ent->GetAbsOrigin()[0], ent->GetAbsOrigin()[1], ent->GetAbsOrigin()[2] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// we want the arms race spawns to shuffle with the regular spawns
|
|
if ( CSGameRules()->IsPlayingGunGameProgressive() )
|
|
ShuffleMasterSpawnPointLists();
|
|
|
|
// sort them to ensure the priority ones are up front
|
|
SortMasterSpawnPointLists();
|
|
|
|
RefreshCurrentSpawnPointLists();
|
|
}
|
|
|
|
void CCSGameRules::RefreshCurrentSpawnPointLists( void )
|
|
{
|
|
// Clear out existing spawn point lists
|
|
m_TerroristSpawnPoints.RemoveAll();
|
|
m_CTSpawnPoints.RemoveAll();
|
|
|
|
m_iSpawnPointCount_Terrorist = 0;
|
|
m_iSpawnPointCount_CT = 0;
|
|
|
|
FOR_EACH_VEC( m_TerroristSpawnPointsMasterList, i )
|
|
{
|
|
SpawnPoint* pSpawnPoint = m_TerroristSpawnPointsMasterList[i];
|
|
if ( pSpawnPoint && pSpawnPoint->IsEnabled() )
|
|
{
|
|
m_iSpawnPointCount_Terrorist++;
|
|
|
|
// Store off the terrorist spawn point
|
|
m_TerroristSpawnPoints.AddToTail( pSpawnPoint );
|
|
}
|
|
}
|
|
|
|
FOR_EACH_VEC( m_CTSpawnPointsMasterList, i )
|
|
{
|
|
SpawnPoint* pSpawnPoint = m_CTSpawnPointsMasterList[i];
|
|
if ( pSpawnPoint && pSpawnPoint->IsEnabled() )
|
|
{
|
|
m_iSpawnPointCount_CT++;
|
|
|
|
// Store off the CT spawn point
|
|
m_CTSpawnPoints.AddToTail( pSpawnPoint );
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
// reset so that T spawns always try to start from 0
|
|
m_iNextTerroristSpawnPoint = 0;
|
|
|
|
// make sure that there are always enough bots
|
|
ConVarRef bot_quota( "bot_quota" );
|
|
int nQuota = UTIL_HumansInGame( true, true ) + MIN( m_iMaxNumTerrorists, m_iSpawnPointCount_Terrorist );
|
|
bot_quota.SetValue( nQuota );
|
|
|
|
//DevMsg( ">> Setting bot_quota to %d\n", nQuota );
|
|
|
|
}
|
|
else
|
|
{
|
|
// Shuffle the spawn points
|
|
ShuffleSpawnPointLists();
|
|
|
|
// Sort the list now that the spawn points have been shuffled
|
|
SortSpawnPointLists();
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::SortSpawnPointLists( void )
|
|
{
|
|
if ( CSGameRules()->IsPlayingGunGameProgressive() )
|
|
{
|
|
// Sort the spawn point lists
|
|
m_TerroristSpawnPoints.Sort( ArmsRaceSpawnPointSortFunction );
|
|
m_CTSpawnPoints.Sort( ArmsRaceSpawnPointSortFunction );
|
|
}
|
|
else
|
|
{
|
|
// Sort the spawn point lists
|
|
m_TerroristSpawnPoints.Sort( SpawnPointSortFunction );
|
|
m_CTSpawnPoints.Sort( SpawnPointSortFunction );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ShuffleSpawnPointLists( void )
|
|
{
|
|
// Shuffle terrorist spawn points
|
|
VectorShuffle( m_TerroristSpawnPoints );
|
|
|
|
// Shuffle CT spawn points
|
|
VectorShuffle( m_CTSpawnPoints );
|
|
}
|
|
|
|
void CCSGameRules::ShuffleMasterSpawnPointLists( void )
|
|
{
|
|
// Shuffle terrorist spawn points
|
|
VectorShuffle( m_TerroristSpawnPointsMasterList );
|
|
|
|
// Shuffle CT spawn points
|
|
VectorShuffle( m_CTSpawnPointsMasterList );
|
|
}
|
|
|
|
void CCSGameRules::SortMasterSpawnPointLists( void )
|
|
{
|
|
if ( CSGameRules()->IsPlayingGunGameProgressive() )
|
|
{
|
|
// Sort the spawn point lists
|
|
m_TerroristSpawnPointsMasterList.Sort( ArmsRaceSpawnPointSortFunction );
|
|
m_CTSpawnPointsMasterList.Sort( ArmsRaceSpawnPointSortFunction );
|
|
|
|
int nSpots = 0;
|
|
// disable all spawns over 10 because we already shoved teh arms race ones up front and
|
|
// if there are enough, disable the ones that aren't flagged as arms race
|
|
FOR_EACH_VEC( m_TerroristSpawnPointsMasterList, i )
|
|
{
|
|
SpawnPoint* pSpawnPoint = m_TerroristSpawnPointsMasterList[i];
|
|
if ( pSpawnPoint && pSpawnPoint->IsEnabled() )
|
|
{
|
|
if ( IsSpawnPointValid( pSpawnPoint, NULL ) == false )
|
|
{
|
|
pSpawnPoint->m_bEnabled = false;
|
|
continue;
|
|
}
|
|
|
|
nSpots++;
|
|
if ( nSpots > 12 )
|
|
pSpawnPoint->m_bEnabled = false;
|
|
}
|
|
}
|
|
|
|
nSpots = 0;
|
|
// disable for the CTs as well
|
|
FOR_EACH_VEC( m_CTSpawnPointsMasterList, i )
|
|
{
|
|
SpawnPoint* pSpawnPoint = m_CTSpawnPointsMasterList[i];
|
|
if ( pSpawnPoint && pSpawnPoint->IsEnabled() )
|
|
{
|
|
if ( IsSpawnPointValid( pSpawnPoint, NULL ) == false )
|
|
{
|
|
pSpawnPoint->m_bEnabled = false;
|
|
continue;
|
|
}
|
|
|
|
nSpots++;
|
|
if ( nSpots > 12 )
|
|
pSpawnPoint->m_bEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if ( !IsPlayingCoopMission() )
|
|
m_TerroristSpawnPointsMasterList.Sort( SpawnPointSortFunction );
|
|
|
|
|
|
m_CTSpawnPointsMasterList.Sort( SpawnPointSortFunction );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ShufflePlayerList( CUtlVector< CCSPlayer* > &playersList )
|
|
{
|
|
// Shuffle players
|
|
VectorShuffle( playersList );
|
|
|
|
// shuffle and sort the T spawn points here too
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
ShuffleSpawnPointLists();
|
|
SortSpawnPointLists();
|
|
}
|
|
}
|
|
|
|
|
|
CBaseEntity*CCSGameRules::GetNextSpawnpoint( int teamNumber )
|
|
{
|
|
CBaseEntity* pRetVal = NULL;
|
|
|
|
if ( teamNumber == TEAM_CT )
|
|
{
|
|
if ( m_iNextCTSpawnPoint >= m_CTSpawnPoints.Count() )
|
|
{
|
|
m_iNextCTSpawnPoint = 0;
|
|
}
|
|
|
|
if ( m_iNextCTSpawnPoint < m_CTSpawnPoints.Count() )
|
|
{
|
|
pRetVal = m_CTSpawnPoints[ m_iNextCTSpawnPoint ];
|
|
m_iNextCTSpawnPoint++;
|
|
}
|
|
}
|
|
else if ( teamNumber == TEAM_TERRORIST )
|
|
{
|
|
if ( m_iNextTerroristSpawnPoint >= m_TerroristSpawnPoints.Count() )
|
|
{
|
|
m_iNextTerroristSpawnPoint = 0;
|
|
}
|
|
|
|
if ( m_iNextTerroristSpawnPoint < m_TerroristSpawnPoints.Count() )
|
|
{
|
|
pRetVal = m_TerroristSpawnPoints[ m_iNextTerroristSpawnPoint ];
|
|
m_iNextTerroristSpawnPoint++;
|
|
}
|
|
}
|
|
|
|
return pRetVal;
|
|
}
|
|
|
|
void CCSGameRules::ShowSpawnPoints( int duration )
|
|
{
|
|
CBaseEntity* ent = NULL;
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) == false )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, duration );
|
|
}
|
|
else if ( !( assert_cast< SpawnPoint* >( ent )->IsEnabled() ) )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 255, 200, duration );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, duration );
|
|
}
|
|
}
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) == false )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, duration );
|
|
}
|
|
else if ( !( assert_cast< SpawnPoint* >( ent )->IsEnabled() ) )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 255, 200, duration );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, duration );
|
|
}
|
|
}
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_deathmatch_spawn" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) )
|
|
{
|
|
if ( !( assert_cast< SpawnPoint* >( ent )->IsEnabled() ) )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 255, 200, duration );
|
|
}
|
|
else if ( IsSpawnPointHiddenFromOtherPlayers( ent, NULL ) )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, duration );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, duration );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, duration );
|
|
}
|
|
}
|
|
|
|
if ( CSGameRules()->IsPlayingGunGameProgressive() )
|
|
{
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_armsrace_terrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) == false )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, duration );
|
|
}
|
|
else if ( !( assert_cast< SpawnPoint* >( ent )->IsEnabled() ) )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 255, 200, duration );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, duration );
|
|
}
|
|
}
|
|
|
|
while ( ( ent = gEntList.FindEntityByClassname( ent, "info_armsrace_counterterrorist" ) ) != NULL )
|
|
{
|
|
if ( IsSpawnPointValid( ent, NULL ) == false )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, duration );
|
|
}
|
|
else if ( !( assert_cast< SpawnPoint* >( ent )->IsEnabled() ) )
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 255, 200, duration );
|
|
}
|
|
else
|
|
{
|
|
NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, duration );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_match.GetCTScore(), m_iNumCT );
|
|
UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", m_match.GetTerroristScore(), m_iNumTerrorist );
|
|
|
|
// let the players know
|
|
char strRestartDelay[64];
|
|
Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay );
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "#SFUI_Second" : "#SFUI_Seconds" );
|
|
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#SFUI_Notice_Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "#SFUI_Second" : "#SFUI_Seconds" );
|
|
|
|
m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay;
|
|
m_bCompleteReset = true;
|
|
m_bGameRestart = true;
|
|
m_bHasMatchStarted = false;
|
|
mp_restartgame.SetValue( 0 );
|
|
}
|
|
}
|
|
|
|
void cc_ScrambleTeams( const CCommand& args )
|
|
{
|
|
if ( UTIL_IsCommandIssuedByServerAdmin() )
|
|
{
|
|
CCSGameRules *pRules = dynamic_cast<CCSGameRules*>( GameRules() );
|
|
|
|
if ( pRules )
|
|
{
|
|
pRules->SetScrambleTeamsOnRestart( true );
|
|
mp_restartgame.SetValue( 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
static ConCommand mp_scrambleteams( "mp_scrambleteams", cc_ScrambleTeams, "Scramble the teams and restart the game" );
|
|
|
|
void cc_SwapTeams( const CCommand& args )
|
|
{
|
|
if ( UTIL_IsCommandIssuedByServerAdmin() )
|
|
{
|
|
CCSGameRules *pRules = dynamic_cast<CCSGameRules*>( GameRules() );
|
|
|
|
if ( pRules )
|
|
{
|
|
pRules->SetSwapTeamsOnRestart( true );
|
|
mp_restartgame.SetValue( 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
static ConCommand mp_swapteams( "mp_swapteams", cc_SwapTeams, "Swap the teams and restart the game" );
|
|
|
|
|
|
// sort function for the list of players that we're going to use to scramble the teams
|
|
int ScramblePlayersSort( CCSPlayer* const *p1, CCSPlayer* const *p2 )
|
|
{
|
|
CCSPlayerResource *pResource = dynamic_cast< CCSPlayerResource * >( g_pPlayerResource );
|
|
|
|
if ( pResource )
|
|
{
|
|
// check the priority
|
|
if ( p1 && p2 && (*p1) && (*p2) && (*p1)->GetScore() > (*p2)->GetScore() )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//////// PAUSE
|
|
void cc_PauseMatch( const CCommand& args )
|
|
{
|
|
if ( UTIL_IsCommandIssuedByServerAdmin() )
|
|
{
|
|
CCSGameRules *pRules = dynamic_cast<CCSGameRules*>( GameRules() );
|
|
|
|
if ( pRules )
|
|
{
|
|
if ( !pRules->IsMatchWaitingForResume() )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Match_Will_Pause" );
|
|
}
|
|
pRules->SetMatchWaitingForResume( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
static ConCommand mp_pause_match( "mp_pause_match", cc_PauseMatch, "Pause the match in the next freeze time" );
|
|
|
|
//////// RESUME
|
|
void cc_ResumeMatch( const CCommand& args )
|
|
{
|
|
if ( UTIL_IsCommandIssuedByServerAdmin() )
|
|
{
|
|
CCSGameRules *pRules = dynamic_cast<CCSGameRules*>( GameRules() );
|
|
|
|
if ( pRules && pRules->IsMatchWaitingForResume() )
|
|
{
|
|
pRules->SetMatchWaitingForResume( false );
|
|
if ( !pRules->IsMatchWaitingForResume() )
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Match_Will_Resume" );
|
|
}
|
|
else
|
|
{
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Match_Will_Pause" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static ConCommand mp_unpause_match( "mp_unpause_match", cc_ResumeMatch, "Resume the match" );
|
|
///////////
|
|
|
|
|
|
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, "#SFUI_Notice_Player_Balanced" );
|
|
UTIL_ClientPrintFilter( m_loyalists, HUD_PRINTCENTER, "#SFUI_Notice_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::AddDroppedWeaponToList( CWeaponCSBase *pWeapon )
|
|
{
|
|
static ConVarRef weapon_max_before_cleanup( "weapon_max_before_cleanup" );
|
|
// NOTE: dont check is removeable here because weapons just thrown aren't removeable, just clean up when we actually do the removals
|
|
if ( pWeapon /*&& pWeapon->IsRemoveable()*/ && weapon_max_before_cleanup.GetInt() > 0 )
|
|
{
|
|
for ( int i = 0; i < m_weaponsDroppedInWorld.Count(); i++ )
|
|
{
|
|
if ( pWeapon == m_weaponsDroppedInWorld[i] )
|
|
return;
|
|
}
|
|
|
|
m_weaponsDroppedInWorld.AddToTail( pWeapon );
|
|
}
|
|
|
|
while ( weapon_max_before_cleanup.GetInt() > 0 && m_weaponsDroppedInWorld.Count() > weapon_max_before_cleanup.GetInt() )
|
|
{
|
|
//CUtlVector< float> m_weaponsDist;
|
|
// get the oldest 5 (or max) weapons
|
|
int nMaxIndex = Min( 5, m_weaponsDroppedInWorld.Count() );
|
|
// for ( int i = 0; i < nMaxIndex; i++ )
|
|
// {
|
|
// m_weaponsDist.AddToTail( 0 );
|
|
// }
|
|
|
|
int index = 0;
|
|
for ( int i = 0; i < nMaxIndex; i++ )
|
|
{
|
|
CWeaponCSBase *pWepTemp = m_weaponsDroppedInWorld[i].Get();
|
|
if ( pWepTemp && pWepTemp->IsRemoveable() )
|
|
{
|
|
// find the first weapon to be far enough away
|
|
for ( int j = 1; j <= MAX_PLAYERS; j++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( j );
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
float flDist = ( pWepTemp->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).Length();
|
|
if ( flDist > 1200 )
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( index > 0 )
|
|
break;
|
|
}
|
|
}
|
|
|
|
CWeaponCSBase *pWep = m_weaponsDroppedInWorld[index].Get();
|
|
if ( pWep && pWep->IsRemoveable() )
|
|
{
|
|
UTIL_Remove( pWep );
|
|
}
|
|
m_weaponsDroppedInWorld.Remove( index );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::RemoveDroppedWeaponFromList( CWeaponCSBase *pWeapon )
|
|
{
|
|
if ( !pWeapon )
|
|
return;
|
|
|
|
for ( int i = 0; i < m_weaponsDroppedInWorld.Count(); i++ )
|
|
{
|
|
if ( pWeapon == m_weaponsDroppedInWorld[i] )
|
|
{
|
|
m_weaponsDroppedInWorld.Remove( i );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ProcessAutoBalance( void )
|
|
{
|
|
// No autobalancing for people who abandon games in queued matchmaking
|
|
if ( IsQueuedMatchmaking() )
|
|
return;
|
|
|
|
/*************** 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 )
|
|
{
|
|
// Queue up impending auto-balance display text
|
|
m_fAutobalanceDisplayTime = gpGlobals->curtime + AUTOBALANCE_TEXT_DELAY;
|
|
m_AutobalanceStatus = AutobalanceStatus::NEXT_ROUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::BalanceTeams( void )
|
|
{
|
|
// No autobalancing for people who abandon games in queued matchmaking
|
|
if ( IsQueuedMatchmaking() )
|
|
return;
|
|
|
|
int iTeamToSwap = TEAM_UNASSIGNED;
|
|
int iNumToSwap;
|
|
|
|
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;
|
|
}
|
|
|
|
m_AutoBalanceTraitors.MakeReliable();
|
|
m_AutoBalanceLoyalists.MakeReliable();
|
|
m_AutoBalanceLoyalists.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 &&
|
|
( pPlayer->GetTeamNumber() == iTeamToSwap ) &&
|
|
( engine->GetPlayerUserId( pPlayer->edict() ) > iHighestUserID ) &&
|
|
( pPlayer->State_Get() != STATE_PICKINGCLASS ) )
|
|
{
|
|
iHighestUserID = engine->GetPlayerUserId( pPlayer->edict() );
|
|
pPlayerToSwap = pPlayer;
|
|
}
|
|
}
|
|
|
|
if ( pPlayerToSwap != NULL )
|
|
{
|
|
m_AutoBalanceTraitors.AddRecipient( pPlayerToSwap );
|
|
m_AutoBalanceLoyalists.RemoveRecipient( pPlayerToSwap );
|
|
pPlayerToSwap->SwitchTeam( iTragetTeam );
|
|
}
|
|
}
|
|
|
|
if ( m_AutoBalanceTraitors.GetRecipientCount() > 0 )
|
|
{
|
|
// Queue up traitor and loyalist display text
|
|
m_fAutobalanceDisplayTime = gpGlobals->curtime + AUTOBALANCE_TEXT_DELAY;
|
|
m_AutobalanceStatus = AutobalanceStatus::THIS_ROUND;
|
|
}
|
|
}
|
|
|
|
|
|
void CCSGameRules::HandleScrambleTeams( void )
|
|
{
|
|
CCSPlayer *pCSPlayer = NULL;
|
|
CUtlVector<CCSPlayer *> pListPlayers;
|
|
|
|
// add all the players (that are on CT or Terrorist) to our temp list
|
|
for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
|
|
{
|
|
pCSPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pCSPlayer && ( pCSPlayer->GetTeamNumber() == TEAM_TERRORIST || pCSPlayer->GetTeamNumber() == TEAM_CT ) )
|
|
{
|
|
pListPlayers.AddToHead( pCSPlayer );
|
|
}
|
|
}
|
|
|
|
// sort the list
|
|
pListPlayers.Sort( ScramblePlayersSort );
|
|
|
|
int team = TEAM_INVALID;
|
|
bool assignToOpposingTeam = false;
|
|
for ( int i = 0 ; i < pListPlayers.Count() ; i++ )
|
|
{
|
|
pCSPlayer = pListPlayers[i];
|
|
|
|
if ( pCSPlayer )
|
|
{
|
|
//First assignment goes to random team
|
|
//Second assignment goes to the opposite
|
|
//Keep alternating until out of players.
|
|
if ( !assignToOpposingTeam )
|
|
{
|
|
team = ( rand() % 2 ) ? TEAM_TERRORIST : TEAM_CT;
|
|
}
|
|
else
|
|
{
|
|
team = ( team == TEAM_TERRORIST ) ? TEAM_CT : TEAM_TERRORIST;
|
|
}
|
|
|
|
pCSPlayer->SwitchTeam( team );
|
|
assignToOpposingTeam = !assignToOpposingTeam;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::OnTeamsSwappedAtRoundReset()
|
|
{
|
|
//
|
|
// This function is called both when the halftime ends and teams swap
|
|
// and when cc_SwapTeams was called on the server and m_bCompleteReset is being performed
|
|
//
|
|
|
|
if ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematch_CT_Surrender ||
|
|
m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematch_T_Surrender )
|
|
{
|
|
if ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematch_CT_Surrender )
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematch_T_Surrender;
|
|
else if ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematch_T_Surrender )
|
|
m_eQueuedMatchmakingRematchState = k_EQueuedMatchmakingRematchState_VoteToRematch_CT_Surrender;
|
|
}
|
|
|
|
//
|
|
// Flip the timeouts as well
|
|
//
|
|
bool bTemp;
|
|
bTemp = m_bTerroristTimeOutActive;
|
|
m_bTerroristTimeOutActive = m_bCTTimeOutActive;
|
|
m_bCTTimeOutActive = bTemp;
|
|
|
|
float flTemp;
|
|
flTemp = m_flTerroristTimeOutRemaining;
|
|
m_flTerroristTimeOutRemaining = m_flCTTimeOutRemaining;
|
|
m_flCTTimeOutRemaining = flTemp;
|
|
|
|
int nTemp = m_nTerroristTimeOuts;
|
|
m_nTerroristTimeOuts = m_nCTTimeOuts;
|
|
m_nCTTimeOuts = nTemp;
|
|
|
|
g_voteControllerT->EndVoteImmediately();
|
|
g_voteControllerCT->EndVoteImmediately();
|
|
}
|
|
|
|
void CCSGameRules::HandleSwapTeams( void )
|
|
{
|
|
CCSPlayer *pCSPlayer = NULL;
|
|
CUtlVector<CCSPlayer *> pListPlayers;
|
|
CUtlVector<CCSPlayer *> pListCoaches;
|
|
|
|
// add all the players (that are on CT or Terrorist) to our temp list
|
|
for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
|
|
{
|
|
pCSPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pCSPlayer && ( pCSPlayer->GetTeamNumber() == TEAM_TERRORIST || pCSPlayer->GetTeamNumber() == TEAM_CT ) )
|
|
{
|
|
pListPlayers.AddToHead( pCSPlayer );
|
|
}
|
|
else if ( pCSPlayer && pCSPlayer->IsCoach() )
|
|
{
|
|
pListCoaches.AddToHead( pCSPlayer );
|
|
}
|
|
|
|
}
|
|
|
|
for ( int i = 0 ; i < pListPlayers.Count() ; i++ )
|
|
{
|
|
pCSPlayer = pListPlayers[i];
|
|
|
|
if ( pCSPlayer )
|
|
{
|
|
int currentTeam = pCSPlayer->GetTeamNumber();
|
|
int newTeam = ( currentTeam == TEAM_TERRORIST ) ? TEAM_CT : TEAM_TERRORIST;
|
|
pCSPlayer->SwitchTeam( newTeam );
|
|
}
|
|
}
|
|
|
|
//coaches last
|
|
for ( int i = 0 ; i < pListCoaches.Count() ; i++ )
|
|
{
|
|
pCSPlayer = pListCoaches[i];
|
|
|
|
if ( pCSPlayer )
|
|
{
|
|
int currentTeam = pCSPlayer->GetAssociatedTeamNumber();
|
|
int newTeam = ( currentTeam == TEAM_TERRORIST ) ? TEAM_CT : TEAM_TERRORIST;
|
|
pCSPlayer->m_iCoachingTeam = newTeam;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Flip the convars for custom team names and flags as well
|
|
//
|
|
|
|
CUtlString sTemp;
|
|
sTemp = mp_teamname_1.GetString();
|
|
mp_teamname_1.SetValue( mp_teamname_2.GetString() );
|
|
mp_teamname_2.SetValue( sTemp.Get() );
|
|
|
|
sTemp = mp_teamflag_1.GetString();
|
|
mp_teamflag_1.SetValue( mp_teamflag_2.GetString() );
|
|
mp_teamflag_2.SetValue( sTemp.Get() );
|
|
|
|
sTemp = mp_teamlogo_1.GetString();
|
|
mp_teamlogo_1.SetValue( mp_teamlogo_2.GetString() );
|
|
mp_teamlogo_2.SetValue( sTemp.Get() );
|
|
|
|
sTemp = mp_teammatchstat_1.GetString();
|
|
mp_teammatchstat_1.SetValue( mp_teammatchstat_2.GetString() );
|
|
mp_teammatchstat_2.SetValue( sTemp.Get() );
|
|
|
|
sTemp = mp_teamscore_1.GetString();
|
|
mp_teamscore_1.SetValue( mp_teamscore_2.GetString() );
|
|
mp_teamscore_2.SetValue( sTemp.Get() );
|
|
|
|
if ( ( mp_teamprediction_pct.GetInt() >= 1 ) && ( mp_teamprediction_pct.GetInt() <= 99 ) )
|
|
mp_teamprediction_pct.SetValue( 100 - mp_teamprediction_pct.GetInt() );
|
|
|
|
OnTeamsSwappedAtRoundReset();
|
|
}
|
|
|
|
|
|
// the following two functions cap the number of players on a team to five instead of basing it on the number of spawn points
|
|
int CCSGameRules::MaxNumPlayersOnTerrTeam()
|
|
{
|
|
if ( IsPlayingCoopMission() )
|
|
return m_iSpawnPointCount_Terrorist;
|
|
|
|
bool bRandomTSpawn = mp_randomspawn.GetInt() == 1 || mp_randomspawn.GetInt() == TEAM_TERRORIST;
|
|
return MIN(m_iMaxNumTerrorists, bRandomTSpawn ? MAX_PLAYERS : m_iSpawnPointCount_Terrorist);
|
|
}
|
|
|
|
int CCSGameRules::MaxNumPlayersOnCTTeam()
|
|
{
|
|
bool bRandomCTSpawn = mp_randomspawn.GetInt() == 1 || mp_randomspawn.GetInt() == TEAM_CT;
|
|
return MIN(m_iMaxNumCTs, bRandomCTSpawn ? MAX_PLAYERS : m_iSpawnPointCount_CT);
|
|
}
|
|
|
|
bool CCSGameRules::TeamFull( int team_id )
|
|
{
|
|
CheckLevelInitialized();
|
|
|
|
switch ( team_id )
|
|
{
|
|
case TEAM_TERRORIST:
|
|
return m_iNumTerrorist >= MaxNumPlayersOnTerrTeam();
|
|
|
|
case TEAM_CT:
|
|
return m_iNumCT >= MaxNumPlayersOnCTTeam();
|
|
|
|
case TEAM_SPECTATOR:
|
|
return GetGlobalTeam( TEAM_SPECTATOR )->GetNumPlayers() >= m_iSpectatorSlotCount;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCSGameRules::WillTeamHaveRoomForPlayer( CCSPlayer* pThePlayer, int newTeam )
|
|
{
|
|
int teamSize = 0;
|
|
|
|
for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ )
|
|
{
|
|
CCSPlayer *pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( clientIndex );
|
|
if ( pPlayer && ( pPlayer != pThePlayer ) )
|
|
{
|
|
if ( pPlayer->GetPendingTeamNumber() == newTeam )
|
|
teamSize++;
|
|
}
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
switch( newTeam )
|
|
{
|
|
case TEAM_TERRORIST:
|
|
result = ( teamSize < MaxNumPlayersOnTerrTeam() );
|
|
break;
|
|
|
|
case TEAM_CT:
|
|
result = ( teamSize < MaxNumPlayersOnCTTeam() );
|
|
break;
|
|
|
|
case TEAM_SPECTATOR:
|
|
result = ( teamSize < m_iSpectatorSlotCount );
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
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_match.GetTerroristScore() < m_match.GetCTScore() )
|
|
{
|
|
team = TEAM_TERRORIST;
|
|
}
|
|
else if ( m_match.GetCTScore() < m_match.GetTerroristScore() )
|
|
{
|
|
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;
|
|
|
|
// Queued matchmaking code forces correct teams already, don't check stacked teams here
|
|
if ( IsQueuedMatchmaking() )
|
|
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" );
|
|
|
|
int nTeamNum = pPlayer->GetTeamNumber();
|
|
|
|
// Player cannot respawn twice in a round
|
|
if ( !pPlayer->IsAbleToInstantRespawn() && !IsWarmupPeriod() )
|
|
{
|
|
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 ( ( m_flRestartRoundTime - gpGlobals->curtime ) > MAX_TIME_TO_WAIT_BEFORE_ENTERING )
|
|
return false;
|
|
|
|
// Only valid team members can spawn
|
|
if ( nTeamNum != TEAM_CT && nTeamNum != TEAM_TERRORIST )
|
|
return false;
|
|
|
|
// Only players with a valid class can spawn
|
|
if ( pPlayer->GetClass() == CS_CLASS_NONE )
|
|
return false;
|
|
|
|
//if ( !IsPlayingGunGameProgressive() && !IsPlayingGunGameDeathmatch() && !IsWarmupPeriod() )
|
|
if ( !pPlayer->IsAbleToInstantRespawn() && !IsWarmupPeriod() )
|
|
{
|
|
// 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 )
|
|
{
|
|
// gurjeets - Between rounds m_fRoundStartTime gets set to cur_time + freeze_time ie
|
|
// sometime in the future. Thus, when we get here, players are still within the
|
|
// round restart time
|
|
// First time into a game, however, m_fRoundStartTime is 0. On this code path,
|
|
// the code that sets this properly only kicks in *after* players are spawned
|
|
// This results in the msg "player_spawned" not getting sent.
|
|
// Hence, added the check for m_fRoundStartTime being 0
|
|
if ( (m_fRoundStartTime != 0) &&
|
|
(gpGlobals->curtime > (m_fRoundStartTime + mp_join_grace_time.GetFloat() )) )
|
|
{
|
|
//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_forcecamera.GetInt() == OBS_ALLOW_NONE )
|
|
{
|
|
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::LoadRoundDataInformation( char const *szFilename )
|
|
{
|
|
#if BACKUPSUPPORTZEROZERO
|
|
//
|
|
// Custom handling for loading 0:0 backup
|
|
//
|
|
if ( !V_strcmp( szFilename, g_szRoundBackupZeroZeroFileName ) )
|
|
{ // Ensure that the warmup starts and stays paused
|
|
extern ConVar mp_warmup_pausetimer;
|
|
mp_warmup_pausetimer.SetValue( 1 );
|
|
StartWarmup();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
KeyValues *kvSaveFile = new KeyValues( "" );
|
|
KeyValues::AutoDelete autodelete_kvSaveFile( kvSaveFile );
|
|
autodelete_kvSaveFile->UsesEscapeSequences( true );
|
|
|
|
if ( !kvSaveFile->LoadFromFile( filesystem, szFilename ) )
|
|
{
|
|
Warning( "Failed to load file: %s\n", szFilename );
|
|
return;
|
|
}
|
|
|
|
UTIL_ClientPrintAll( HUD_PRINTTALK, CFmtStr( "Restoring match backup %s, score %d:%d after round %d\n",
|
|
kvSaveFile->GetString( "timestamp" ),
|
|
kvSaveFile->GetInt( "FirstHalfScore/team1" ) + kvSaveFile->GetInt( "SecondHalfScore/team1" ) + kvSaveFile->GetInt( "OvertimeScore/team1" ),
|
|
kvSaveFile->GetInt( "FirstHalfScore/team2" ) + kvSaveFile->GetInt( "SecondHalfScore/team2" ) + kvSaveFile->GetInt( "OvertimeScore/team2" ),
|
|
kvSaveFile->GetInt( "round" ) ) );
|
|
|
|
int numRoundsPlayed = kvSaveFile->GetInt( "round" );
|
|
int numOvertimePlaying = kvSaveFile->GetInt( "OvertimeScore/OvertimeID" );
|
|
bool bResetPlayerAccounts =
|
|
( ( numRoundsPlayed < mp_maxrounds.GetInt() ) || !mp_overtime_maxrounds.GetInt() )
|
|
? ( numRoundsPlayed == ( mp_maxrounds.GetInt() / 2 ) )
|
|
: !( ( numRoundsPlayed - mp_maxrounds.GetInt() ) % ( mp_overtime_maxrounds.GetInt() / 2 ) );
|
|
|
|
// Send all CT and T players to spectators for now
|
|
int nSavedSpectatorSlotsCount = m_iSpectatorSlotCount;
|
|
m_iSpectatorSlotCount = INT_MAX;
|
|
m_bForceTeamChangeSilent = true; // silence all the messages about players changing teams
|
|
m_bLoadingRoundBackupData = true; // make sure the game rules don't do any additional round restarts
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if ( pPlayer && pPlayer->IsConnected() && !pPlayer->IsBot() &&
|
|
( ( pPlayer->GetTeamNumber() == TEAM_CT ) || ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) ||
|
|
( pPlayer->GetPendingTeamNumber() == TEAM_CT ) || ( pPlayer->GetPendingTeamNumber() == TEAM_TERRORIST ) ) )
|
|
{
|
|
pPlayer->HandleCommand_JoinTeam( TEAM_SPECTATOR );
|
|
pPlayer->SetPendingTeamNum( TEAM_UNASSIGNED );
|
|
}
|
|
}
|
|
|
|
// Wipe cached accounts information and team assignments
|
|
if ( !IsQueuedMatchmaking() )
|
|
{
|
|
m_mapQueuedMatchmakingPlayersData.PurgeAndDeleteElements();
|
|
}
|
|
|
|
//
|
|
// Reset match and streak data
|
|
//
|
|
{
|
|
m_iTotalRoundsPlayed = 0;
|
|
|
|
// Reset score info
|
|
m_match.Reset();
|
|
m_iNumConsecutiveTerroristLoses = bResetPlayerAccounts ? 0 : kvSaveFile->GetInt( "History/NumConsecutiveTerroristLoses" );
|
|
m_iNumConsecutiveCTLoses = bResetPlayerAccounts ? 0 : kvSaveFile->GetInt( "History/NumConsecutiveCTLoses" );
|
|
|
|
m_iAccountTerrorist = bResetPlayerAccounts ? 0 : kvSaveFile->GetInt( "History/ExtraAccountTerrorist" );
|
|
m_iAccountCT = bResetPlayerAccounts ? 0 : kvSaveFile->GetInt( "History/ExtraAccountCT" );
|
|
|
|
m_iLoserBonus = bResetPlayerAccounts ? TeamCashAwardValue( TeamCashAward::LOSER_BONUS ) : kvSaveFile->GetInt( "History/LoserBonus" );
|
|
|
|
// Reset hostage spawn indices
|
|
m_arrSelectedHostageSpawnIndices.RemoveAll();
|
|
if ( char const *szHostageSpawnIndices = kvSaveFile->GetString( "History/HostageSpawnIndices", NULL ) )
|
|
{
|
|
CUtlVector< char* > tagStrings;
|
|
V_SplitString( szHostageSpawnIndices, ",", tagStrings );
|
|
m_arrSelectedHostageSpawnIndices.EnsureCapacity( tagStrings.Count() );
|
|
FOR_EACH_VEC( tagStrings, iTagString )
|
|
{
|
|
m_arrSelectedHostageSpawnIndices.AddToTail( Q_atoi( tagStrings[iTagString] ) );
|
|
}
|
|
tagStrings.PurgeAndDeleteElements();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process save data, disregarding team assignment
|
|
// we'll figure out which team players are supposed to be on later
|
|
//
|
|
m_match.AddCTWins( kvSaveFile->GetInt( "FirstHalfScore/team1" ) );
|
|
m_match.AddTerroristWins( kvSaveFile->GetInt( "FirstHalfScore/team2" ) );
|
|
if ( numRoundsPlayed >= ( mp_maxrounds.GetInt() / 2 ) )
|
|
{
|
|
m_match.SetPhase( GAMEPHASE_PLAYING_SECOND_HALF );
|
|
m_match.AddCTWins( kvSaveFile->GetInt( "SecondHalfScore/team1" ) );
|
|
m_match.AddTerroristWins( kvSaveFile->GetInt( "SecondHalfScore/team2" ) );
|
|
}
|
|
if ( ( numRoundsPlayed >= mp_maxrounds.GetInt() ) || numOvertimePlaying )
|
|
{
|
|
int nOvertimeBasedOnRoundsPlayed = mp_overtime_maxrounds.GetInt() ? ( numRoundsPlayed - mp_maxrounds.GetInt() ) / mp_overtime_maxrounds.GetInt() : 0;
|
|
numOvertimePlaying = MAX( numOvertimePlaying, nOvertimeBasedOnRoundsPlayed + 1 );
|
|
m_match.GoToOvertime( numOvertimePlaying );
|
|
|
|
if ( mp_overtime_maxrounds.GetInt() &&
|
|
( ( ( numRoundsPlayed - mp_maxrounds.GetInt() ) % mp_overtime_maxrounds.GetInt() ) < ( mp_overtime_maxrounds.GetInt() / 2 ) ) )
|
|
m_match.SetPhase( GAMEPHASE_PLAYING_FIRST_HALF );
|
|
|
|
m_match.AddCTWins( kvSaveFile->GetInt( "OvertimeScore/team1" ) );
|
|
m_match.AddTerroristWins( kvSaveFile->GetInt( "OvertimeScore/team2" ) );
|
|
}
|
|
|
|
// Switch team scores if they are supposed to be switched now
|
|
bool bAreTeamsPlayingSwitchedSides = AreTeamsPlayingSwitchedSides();
|
|
if ( bAreTeamsPlayingSwitchedSides )
|
|
{
|
|
m_match.SwapTeamScores();
|
|
}
|
|
|
|
for ( int r = 0; r < m_iMatchStats_RoundResults.Count(); r++ )
|
|
{
|
|
char szKey[30];
|
|
|
|
V_sprintf_safe( szKey, "RoundResults/round%d", r + 1 );
|
|
|
|
m_iMatchStats_RoundResults.GetForModify( r ) = kvSaveFile->GetInt( szKey );
|
|
}
|
|
for ( int r = 0; r < m_iMatchStats_PlayersAlive_T.Count(); r++ )
|
|
{
|
|
char szKey[30];
|
|
|
|
V_sprintf_safe( szKey, "PlayersAliveT/round%d", r + 1 );
|
|
|
|
m_iMatchStats_PlayersAlive_T.GetForModify( r ) = kvSaveFile->GetInt( szKey );
|
|
}
|
|
for ( int r = 0; r < m_iMatchStats_PlayersAlive_CT.Count(); r++ )
|
|
{
|
|
char szKey[30];
|
|
|
|
V_sprintf_safe( szKey, "PlayersAliveCT/round%d", r + 1 );
|
|
|
|
m_iMatchStats_PlayersAlive_CT.GetForModify( r ) = kvSaveFile->GetInt( szKey );
|
|
}
|
|
|
|
// Now assign the players to correct teams with correct stats
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
if ( pPlayer && pPlayer->IsConnected() && !pPlayer->IsBot() )
|
|
{
|
|
CSteamID steamIdHumanPlayer;
|
|
if ( pPlayer->GetSteamID( &steamIdHumanPlayer ) )
|
|
{
|
|
int iTeamOrder = 0;
|
|
KeyValues *kvPlayerData = NULL;
|
|
for ( ; iTeamOrder < 2; ++ iTeamOrder )
|
|
{
|
|
kvPlayerData = kvSaveFile->FindKey( CFmtStr( "PlayersOnTeam%d/%u", iTeamOrder + 1, steamIdHumanPlayer.GetAccountID() ), false );
|
|
if ( kvPlayerData )
|
|
break;
|
|
}
|
|
if ( !kvPlayerData )
|
|
{
|
|
Warning( "Server checkpoint has no information for player %u: %s\n", steamIdHumanPlayer.GetAccountID(), pPlayer->GetPlayerName() );
|
|
}
|
|
else
|
|
{
|
|
int iRulesTeam = bAreTeamsPlayingSwitchedSides ? ( (iTeamOrder == 0)?TEAM_TERRORIST:TEAM_CT ) : ( (iTeamOrder == 0)?TEAM_CT:TEAM_TERRORIST );
|
|
pPlayer->HandleCommand_JoinTeam( iRulesTeam ); // join the correct team
|
|
pPlayer->HandleCommand_JoinClass(); // force join class now
|
|
pPlayer->MarkAsNotReceivingMoneyNextRound(); // after loading backup we shouldn't give out extra end round money
|
|
|
|
pPlayer->ResetFragCount();
|
|
pPlayer->SetEnemyKillTrackInfo( kvPlayerData->GetInt( "enemyKs" ), kvPlayerData->GetInt( "enemyHSs" ), kvPlayerData->GetInt( "enemy3Ks" ), kvPlayerData->GetInt( "enemy4Ks" ), kvPlayerData->GetInt( "enemy5Ks" ), kvPlayerData->GetInt( "enemyKAg" ) );
|
|
pPlayer->SetEnemyFirstKills( kvPlayerData->GetInt( "firstKs" ), kvPlayerData->GetInt( "clutchKs" ) );
|
|
pPlayer->SetEnemyWeaponKills( kvPlayerData->GetInt( "kills_weapon_pistol" ), kvPlayerData->GetInt( "kills_weapon_sniper" ) );
|
|
|
|
pPlayer->IncrementFragCount( kvPlayerData->GetInt( "kills" ), -1 ); // -1 headshots is a special flag indicating that we are faking the call here, but it will still record kill info in QMM
|
|
pPlayer->ResetAssistsCount();
|
|
pPlayer->IncrementAssistsCount( kvPlayerData->GetInt( "assists" ) );
|
|
pPlayer->ResetDeathCount();
|
|
pPlayer->IncrementDeathCount( kvPlayerData->GetInt( "deaths" ) );
|
|
|
|
pPlayer->SetNumMVPs( kvPlayerData->GetInt( "mvps" ) );
|
|
|
|
pPlayer->ClearContributionScore();
|
|
pPlayer->AddContributionScore( kvPlayerData->GetInt( "score" ) );
|
|
|
|
pPlayer->ResetNumRoundKills();
|
|
|
|
int iCashAccount = kvPlayerData->GetInt( "cash" );
|
|
if ( bResetPlayerAccounts )
|
|
{
|
|
iCashAccount = GetOvertimePlaying() ? mp_overtime_startmoney.GetInt() : mp_startmoney.GetInt();
|
|
}
|
|
pPlayer->AddAccount( iCashAccount - pPlayer->GetAccountBalance(), true, false, NULL );
|
|
|
|
pPlayer->RemoveAllItems( true );
|
|
if ( !bResetPlayerAccounts )
|
|
{
|
|
for ( KeyValues *kvItem = kvPlayerData->FindKey( "Items" )->GetFirstValue(); kvItem; kvItem = kvItem->GetNextValue() )
|
|
{
|
|
pPlayer->GiveNamedItem( kvItem->GetString() );
|
|
}
|
|
|
|
pPlayer->SetArmorValue( kvPlayerData->GetInt( "armor" ) );
|
|
pPlayer->m_bHasHelmet = kvPlayerData->GetBool( "helmet" );
|
|
|
|
if ( kvPlayerData->GetBool( "defusekit" ) && ( pPlayer->GetTeamNumber() == TEAM_CT ) )
|
|
pPlayer->GiveDefuser();
|
|
}
|
|
|
|
for ( int r = 0; r < MAX_MATCH_STATS_ROUNDS; r++ )
|
|
{
|
|
char szKey[64] = {};
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/Kills/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_Kills.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/Damage/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_Damage.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/EquipmentValue/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_EquipmentValue.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/MoneySaved/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_MoneySaved.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/KillReward/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_KillReward.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/LiveTime/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_LiveTime.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/Deaths/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_Deaths.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/Assists/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_Assists.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/HeadShotKills/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_HeadShotKills.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/Objective/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_Objective.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/CashEarned/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_CashEarned.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/UtilityDamage/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_UtilityDamage.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
V_snprintf( szKey, ARRAYSIZE( szKey ), "MatchStats/EnemiesFlashed/round%d", r + 1 );
|
|
pPlayer->m_iMatchStats_EnemiesFlashed.GetForModify( r ) = ( r < numRoundsPlayed ) ? kvPlayerData->GetInt( szKey ) : 0;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore spectators
|
|
m_iSpectatorSlotCount = nSavedSpectatorSlotsCount;
|
|
m_bForceTeamChangeSilent = false;
|
|
|
|
// Tell that we have loaded the checkpoint
|
|
Warning( "Loaded server checkpoint %s, starting match with score %d:%d after round %d\n",
|
|
kvSaveFile->GetString( "timestamp" ),
|
|
kvSaveFile->GetInt( "FirstHalfScore/team1" ) + kvSaveFile->GetInt( "SecondHalfScore/team1" ),
|
|
kvSaveFile->GetInt( "FirstHalfScore/team2" ) + kvSaveFile->GetInt( "SecondHalfScore/team2" ),
|
|
kvSaveFile->GetInt( "round" ) );
|
|
|
|
// Make sure we restart without warmup if warmup was active
|
|
m_bWarmupPeriod = false;
|
|
m_bCompleteReset = false;
|
|
m_fWarmupPeriodStart = -1;
|
|
m_flRestartRoundTime = 0;
|
|
|
|
// Notify clients of potential phase change
|
|
if ( IGameEvent * event = gameeventmanager->CreateEvent( "announce_phase_end" ) )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
m_phaseChangeAnnouncementTime = 0.0f;
|
|
|
|
if ( mp_backup_restore_load_autopause.GetBool() )
|
|
{
|
|
// pause the match
|
|
SetMatchWaitingForResume( true );
|
|
}
|
|
|
|
// Restart round
|
|
EndRound();
|
|
}
|
|
|
|
static void Helper_CleanupStringForSaveRoundDataInformation( char *pch )
|
|
{
|
|
int nLength = Q_strlen( pch );
|
|
char *pchStart = pch;
|
|
for ( ; *pch; ++pch )
|
|
{
|
|
if (
|
|
( ( pch[ 0 ] >= 'a' ) && ( pch[ 0 ] <= 'z' ) ) ||
|
|
( ( pch[ 0 ] >= 'A' ) && ( pch[ 0 ] <= 'Z' ) ) ||
|
|
( ( pch[ 0 ] >= '0' ) && ( pch[ 0 ] <= '9' ) )
|
|
)
|
|
;
|
|
else
|
|
{
|
|
Q_memmove( pch, pch + 1, nLength - ( pch - pchStart ) );
|
|
--pch;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::SaveRoundDataInformation( char const *szFilenameOverride )
|
|
{
|
|
/** Removed for partner depot **/
|
|
}
|
|
|
|
CCSGameRules::CQMMPlayerData_t * CCSGameRules::QueuedMatchmakingPlayersDataFindOrCreate( CCSPlayer *pPlayer )
|
|
{
|
|
CSteamID steamIdHumanPlayer;
|
|
if ( !pPlayer->GetSteamID( &steamIdHumanPlayer ) )
|
|
return NULL;
|
|
if ( !steamIdHumanPlayer.IsValid() || !steamIdHumanPlayer.BIndividualAccount() )
|
|
return NULL;
|
|
if ( steamIdHumanPlayer.GetAccountID() != pPlayer->GetHumanPlayerAccountID() )
|
|
return NULL;
|
|
|
|
CCSGameRules::CQMMPlayerData_t *pQMM = QueuedMatchmakingPlayersDataFind( steamIdHumanPlayer.GetAccountID() );
|
|
if ( pQMM ) // Already exists
|
|
return pQMM;
|
|
|
|
// There are some cases where we cannot create the player data
|
|
// or are supposed to have all entries previously created upon GC request
|
|
if ( IsQueuedMatchmaking() )
|
|
return NULL;
|
|
|
|
// Prepare for creating the entry
|
|
bool bControllingBot = pPlayer->IsControllingBot();
|
|
bool bTeamsArePlayingSwitchedSides = AreTeamsPlayingSwitchedSides();
|
|
int iTeamOrder = 0; // see "CCSGameRules::SaveRoundDataInformation" processing
|
|
switch ( pPlayer->GetTeamNumber() )
|
|
{
|
|
case TEAM_CT:
|
|
case TEAM_TERRORIST:
|
|
iTeamOrder = ( ( pPlayer->GetTeamNumber() == TEAM_CT ) == bTeamsArePlayingSwitchedSides ) ? 1 : 0;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Otherwise it is valid to create a new entry
|
|
//
|
|
CQMMPlayerData_t &qmmPlayerData = *new CQMMPlayerData_t;
|
|
qmmPlayerData.m_uiPlayerAccountId = steamIdHumanPlayer.GetAccountID();
|
|
qmmPlayerData.m_iDraftIndex = iTeamOrder * 5;
|
|
Q_strncpy( qmmPlayerData.m_chPlayerName, pPlayer->GetPlayerName(), sizeof( qmmPlayerData.m_chPlayerName ) );
|
|
|
|
qmmPlayerData.m_numKills = bControllingBot ? pPlayer->GetBotPreControlData().m_iFrags : pPlayer->FragCount();
|
|
qmmPlayerData.m_numAssists = bControllingBot ? pPlayer->GetBotPreControlData().m_iAssists : pPlayer->AssistsCount();
|
|
qmmPlayerData.m_numDeaths = bControllingBot ? pPlayer->GetBotPreControlData().m_iDeaths : pPlayer->DeathCount();
|
|
qmmPlayerData.m_numScorePoints = pPlayer->GetScore();
|
|
qmmPlayerData.m_numMVPs = pPlayer->GetNumMVPs();
|
|
qmmPlayerData.m_cash = bControllingBot ? pPlayer->GetBotPreControlData().m_iAccount : pPlayer->GetAccountBalance();
|
|
qmmPlayerData.m_bReceiveNoMoneyNextRound = !pPlayer->DoesPlayerGetRoundStartMoney();
|
|
|
|
int nKills, iEnemyKillHeadshots, iEnemy3Ks, iEnemy4Ks, iEnemy5Ks, iEnemyKillsAgg;
|
|
pPlayer->GetEnemyKillTrackInfo( nKills, iEnemyKillHeadshots, iEnemy3Ks, iEnemy4Ks, iEnemy5Ks, iEnemyKillsAgg );
|
|
qmmPlayerData.m_numEnemyKills = nKills;
|
|
qmmPlayerData.m_numEnemyKillHeadshots = iEnemyKillHeadshots;
|
|
qmmPlayerData.m_numEnemy3Ks = iEnemy3Ks;
|
|
qmmPlayerData.m_numEnemy4Ks = iEnemy4Ks;
|
|
qmmPlayerData.m_numEnemy5Ks = iEnemy5Ks;
|
|
qmmPlayerData.m_numEnemyKillsAgg = iEnemyKillsAgg;
|
|
|
|
qmmPlayerData.m_numRoundsWon = pPlayer->m_iRoundsWon;
|
|
|
|
for ( int i = 0; i < MAX_MATCH_STATS_ROUNDS; i++ )
|
|
{
|
|
qmmPlayerData.m_iMatchStats_Kills[ i ] = pPlayer->m_iMatchStats_Kills.Get( i );
|
|
qmmPlayerData.m_iMatchStats_Damage[ i ] = pPlayer->m_iMatchStats_Damage.Get( i );
|
|
qmmPlayerData.m_iMatchStats_MoneySaved[ i ] = pPlayer->m_iMatchStats_MoneySaved.Get( i );
|
|
qmmPlayerData.m_iMatchStats_EquipmentValue[ i ] = pPlayer->m_iMatchStats_EquipmentValue.Get( i );
|
|
qmmPlayerData.m_iMatchStats_KillReward[ i ] = pPlayer->m_iMatchStats_KillReward.Get( i );
|
|
qmmPlayerData.m_iMatchStats_LiveTime[ i ] = pPlayer->m_iMatchStats_LiveTime.Get( i );
|
|
qmmPlayerData.m_iMatchStats_Deaths[ i ] = pPlayer->m_iMatchStats_Deaths.Get( i );
|
|
qmmPlayerData.m_iMatchStats_Assists[ i ] = pPlayer->m_iMatchStats_Assists.Get( i );
|
|
qmmPlayerData.m_iMatchStats_HeadShotKills[ i ] = pPlayer->m_iMatchStats_HeadShotKills.Get( i );
|
|
qmmPlayerData.m_iMatchStats_Objective[ i ] = pPlayer->m_iMatchStats_Objective.Get( i );
|
|
qmmPlayerData.m_iMatchStats_CashEarned[ i ] = pPlayer->m_iMatchStats_CashEarned.Get( i );
|
|
qmmPlayerData.m_iMatchStats_UtilityDamage[ i ] = pPlayer->m_iMatchStats_UtilityDamage.Get( i );
|
|
qmmPlayerData.m_iMatchStats_EnemiesFlashed[ i ] = pPlayer->m_iMatchStats_EnemiesFlashed.Get( i );
|
|
}
|
|
|
|
m_mapQueuedMatchmakingPlayersData.InsertOrReplace( qmmPlayerData.m_uiPlayerAccountId, &qmmPlayerData );
|
|
|
|
return &qmmPlayerData;
|
|
}
|
|
|
|
void CCSGameRules::IncrementAndTerminateRound( float tmDelay, int reason )
|
|
{
|
|
bool roundIsAlreadyOver = ( CSGameRules()->m_iRoundWinStatus != WINNER_NONE );
|
|
if ( roundIsAlreadyOver )
|
|
return;
|
|
|
|
if ( reason == Round_Draw )
|
|
{
|
|
m_match.IncrementRound( 1 );
|
|
}
|
|
else if ( reason == Terrorists_Win )
|
|
{
|
|
m_match.AddTerroristWins( 1 );
|
|
}
|
|
else if ( reason == CTs_Win )
|
|
{
|
|
m_match.AddCTWins( 1 );
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST ) )
|
|
pTeam->MarkSurrendered();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( !"IncrementAndTerminateRound not implemented for the reason passed" );
|
|
return;
|
|
}
|
|
|
|
TerminateRound( tmDelay, reason );
|
|
}
|
|
|
|
static inline int GetNumPlayers( CTeam *pTeam )
|
|
{
|
|
return pTeam ? pTeam->GetNumPlayers() : 0;
|
|
}
|
|
|
|
void CCSGameRules::TerminateRound(float tmDelay, int iReason )
|
|
{
|
|
if ( m_iRoundWinStatus != WINNER_NONE )
|
|
return;
|
|
|
|
variant_t emptyVariant;
|
|
int iWinnerTeam = WINNER_NONE;
|
|
const char *text = "UNKNOWN";
|
|
|
|
// extend the time a bit if we have items to display
|
|
float flItemDelay = tmDelay;
|
|
// if ( m_ItemsPtrDroppedDuringMatch.Count() > 0 && !IsPlayingAnyCompetitiveStrictRuleset() )
|
|
// flItemDelay += MIN(5, m_ItemsPtrDroppedDuringMatch.Count())*0.75;
|
|
|
|
// UTIL_ClientPrintAll( HUD_PRINTCENTER, sentence );
|
|
|
|
if ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematch_T_Surrender )
|
|
{
|
|
iReason = Terrorists_Surrender;
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST ) )
|
|
pTeam->MarkSurrendered();
|
|
}
|
|
else if ( m_eQueuedMatchmakingRematchState == k_EQueuedMatchmakingRematchState_VoteToRematch_CT_Surrender )
|
|
{
|
|
iReason = CTs_Surrender;
|
|
if ( CTeam *pTeam = GetGlobalTeam( TEAM_CT ) )
|
|
pTeam->MarkSurrendered();
|
|
}
|
|
|
|
switch ( iReason )
|
|
{
|
|
// Terror wins:
|
|
case Target_Bombed:
|
|
text = "#SFUI_Notice_Target_Bombed";
|
|
iWinnerTeam = WINNER_TER;
|
|
break;
|
|
|
|
case Terrorists_Escaped:
|
|
text = "#SFUI_Notice_Terrorists_Escaped";
|
|
iWinnerTeam = WINNER_TER;
|
|
break;
|
|
|
|
case Terrorists_Win:
|
|
text = "#SFUI_Notice_Terrorists_Win";
|
|
iWinnerTeam = WINNER_TER;
|
|
break;
|
|
|
|
case Hostages_Not_Rescued:
|
|
text = "#SFUI_Notice_Hostages_Not_Rescued";
|
|
iWinnerTeam = WINNER_TER;
|
|
break;
|
|
|
|
case Terrorists_Planted:
|
|
text = "#SFUI_Notice_Terrorists_Planted";
|
|
iWinnerTeam = WINNER_TER;
|
|
break;
|
|
|
|
case CTs_ReachedHostage:
|
|
text = "#SFUI_Notice_CTs_ReachedHostage";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case CTs_PreventEscape:
|
|
text = "#SFUI_Notice_CTs_PreventEscape";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case Escaping_Terrorists_Neutralized:
|
|
text = "#SFUI_Notice_Escaping_Terrorists_Neutralized";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case Bomb_Defused:
|
|
text = "#SFUI_Notice_Bomb_Defused";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case CTs_Win:
|
|
text = "#SFUI_Notice_CTs_Win";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case All_Hostages_Rescued:
|
|
text = "#SFUI_Notice_All_Hostages_Rescued";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case Target_Saved:
|
|
text = "#SFUI_Notice_Target_Saved";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case Terrorists_Not_Escaped:
|
|
text = "#SFUI_Notice_Terrorists_Not_Escaped";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
// no winners:
|
|
case Game_Commencing:
|
|
text = "#SFUI_Notice_Game_Commencing";
|
|
iWinnerTeam = WINNER_DRAW;
|
|
break;
|
|
|
|
case Round_Draw:
|
|
text = "#SFUI_Notice_Round_Draw";
|
|
iWinnerTeam = WINNER_DRAW;
|
|
break;
|
|
|
|
case Terrorists_Surrender:
|
|
text = "#SFUI_Notice_Terrorists_Surrender";
|
|
iWinnerTeam = WINNER_CT;
|
|
break;
|
|
|
|
case CTs_Surrender:
|
|
text = "#SFUI_Notice_CTs_Surrender";
|
|
iWinnerTeam = WINNER_TER;
|
|
break;
|
|
|
|
default:
|
|
DevMsg("TerminateRound: unknown round end ID %i\n", iReason );
|
|
break;
|
|
}
|
|
|
|
m_iRoundWinStatus = iWinnerTeam;
|
|
m_eRoundWinReason = iReason;
|
|
m_flRestartRoundTime = gpGlobals->curtime + flItemDelay;
|
|
|
|
if ( ( m_iRoundWinStatus == WINNER_TER ) || ( m_iRoundWinStatus == WINNER_CT ) )
|
|
{ // If the round is ending and there's a pending hook for MVP calculation
|
|
// and player scoring then run these calculations here before the round
|
|
// officially ends and the game goes on to save round stats or goes to
|
|
// intermission and submits match outcome
|
|
if ( m_pfnCalculateEndOfRoundMVPHook )
|
|
m_pfnCalculateEndOfRoundMVPHook->CalculateEndOfRoundMVP();
|
|
else // run default MVP rules without any special hooks
|
|
CalculateEndOfRoundMVP();
|
|
}
|
|
|
|
if ( ( ( m_iRoundWinStatus == WINNER_TER ) || ( m_iRoundWinStatus == WINNER_CT ) )
|
|
&& Helper_mp_backup_round_IsEnabled() )
|
|
{
|
|
SaveRoundDataInformation();
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if (pPlayer)
|
|
{
|
|
// have all players do any end of round bookkeeping
|
|
pPlayer->HandleEndOfRound();
|
|
}
|
|
}
|
|
|
|
// [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, (e_RoundEndReason)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 = GetRoundElapsedTime();
|
|
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);
|
|
|
|
if ( ( pPlayer->GetTeamNumber() == iWinnerTeam ) && ( IsPlayingClassic() ) )
|
|
{
|
|
++ pPlayer->m_iRoundsWon;
|
|
|
|
// Keep track in QMM data
|
|
if ( pPlayer->GetHumanPlayerAccountID() )
|
|
{
|
|
if ( CCSGameRules::CQMMPlayerData_t *pQMM = CSGameRules()->QueuedMatchmakingPlayersDataFind( pPlayer->GetHumanPlayerAccountID() ) )
|
|
{
|
|
pQMM->m_numRoundsWon = pPlayer->m_iRoundsWon;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopGuardian() &&
|
|
(iReason == CTs_ReachedHostage || iReason == Terrorists_Planted) &&
|
|
(pPlayer->GetTeamNumber() == TEAM_CT || pPlayer->GetTeamNumber() == TEAM_TERRORIST) )
|
|
{
|
|
color32_s clr = {0,0,0,255};
|
|
UTIL_ScreenFade( pPlayer, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT );
|
|
}
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
color32_s clr = { 0, 0, 0, 255 };
|
|
UTIL_ScreenFade( pPlayer, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsPlayingCoopGuardian() && ( iReason == CTs_ReachedHostage || iReason == Terrorists_Planted ) )
|
|
FreezePlayers();
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
CGameCoopMissionManager *pManager = GetCoopMissionManager();
|
|
// we really dont have one
|
|
if ( !pManager )
|
|
DevMsg( "Coop mission map is missing a game_coopmission_manager entity. You can't fire round ended outputs without it!\n" );
|
|
else if ( iReason == Terrorists_Win )
|
|
{
|
|
if ( GetRoundRemainingTime() <= 0 )
|
|
pManager->SetRoundLostTime();
|
|
else
|
|
pManager->SetRoundLostKilled();
|
|
}
|
|
}
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "round_end" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "winner", iWinnerTeam );
|
|
event->SetInt( "reason", iReason );
|
|
event->SetString( "message", text );
|
|
event->SetInt( "priority", 6 ); // round_end
|
|
event->SetInt( "player_count", GetNumPlayers( GetGlobalTeam( TEAM_CT ) ) + GetNumPlayers( GetGlobalTeam( TEAM_TERRORIST ) ) );
|
|
if ( ( iWinnerTeam == TEAM_CT ) && ( iReason == Bomb_Defused ) )
|
|
{
|
|
// Check if there are Terrorists alive
|
|
bool bTerroristsAlive = false;
|
|
float flSecondsTillDetonationRemaining = 0.0f;
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pCheckPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pCheckPlayer )
|
|
continue;
|
|
if ( ( pCheckPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
|
&& pCheckPlayer->IsAlive() )
|
|
{
|
|
bTerroristsAlive = true;
|
|
break;
|
|
}
|
|
if ( pCheckPlayer->GetTeamNumber() == TEAM_CT )
|
|
{
|
|
float flTimeTillDetonation = pCheckPlayer->GetDefusedBombWithThisTimeRemaining();
|
|
if ( flTimeTillDetonation > flSecondsTillDetonationRemaining )
|
|
flSecondsTillDetonationRemaining = flTimeTillDetonation;
|
|
}
|
|
}
|
|
if ( !bTerroristsAlive && ( flSecondsTillDetonationRemaining > 0 ) )
|
|
{ // Can only play legacy radio if no terrorists are alive (so all players are focused on the defuse)
|
|
if ( flSecondsTillDetonationRemaining < 1.0f )
|
|
{ // Always play the coolest brag for a close defuse
|
|
event->SetInt( "legacy", 3 );
|
|
}
|
|
else if ( flSecondsTillDetonationRemaining < 3.0f )
|
|
{
|
|
if ( RandomFloat() < 0.75f ) // 75% chance for a medium tier brag
|
|
event->SetInt( "legacy", 2 );
|
|
}
|
|
else if ( flSecondsTillDetonationRemaining < 7.0f )
|
|
{
|
|
if ( RandomFloat() < 0.5f ) // 50% chance for a low tier brag
|
|
event->SetInt( "legacy", 1 );
|
|
}
|
|
}
|
|
}
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
if ( ( iReason == CTs_Surrender || iReason == Terrorists_Surrender ) && !IsQueuedMatchmaking() )
|
|
{
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
GoToIntermission();
|
|
}
|
|
|
|
if ( ( GetMapRemainingTime() == 0.0f ) && !IsQueuedMatchmaking() )
|
|
{
|
|
UTIL_LogPrintf("World triggered \"Intermission_Time_Limit\"\n");
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
GoToIntermission();
|
|
}
|
|
|
|
// Only update bot difficulty if playing in an Online-game (In offline mode, bot difficulty is set by user)
|
|
if ( !IsPlayingOffline() )
|
|
{
|
|
// Determine the difficulty level that the bots should be at to compete
|
|
ModifyRealtimeBotDifficulty();
|
|
}
|
|
|
|
if ( ( static_cast< e_RoundEndReason > ( iReason ) != Game_Commencing ) && !m_bLoadingRoundBackupData )
|
|
{
|
|
// Perform round-related processing at the point when a round winner has been determined
|
|
RoundWin();
|
|
}
|
|
|
|
// This is a fantastic opportunity to submit round results to GC
|
|
if ( iReason != Game_Commencing )
|
|
{
|
|
ReportRoundEndStatsToGC();
|
|
|
|
if ( Helper_ShouldBroadcastCoopScoreLeaderboardData() )
|
|
{
|
|
CReliableBroadcastRecipientFilter filter;
|
|
CCSUsrMsg_ScoreLeaderboardData msg;
|
|
Helper_FillScoreLeaderboardData( *msg.mutable_data() );
|
|
SendUserMessage( filter, CS_UM_ScoreLeaderboardData, msg );
|
|
}
|
|
|
|
if ( IsPlayingCoopGuardian()
|
|
&& ( iWinnerTeam == ( IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT ) ) )
|
|
{
|
|
// If human team wins then end the match right now!
|
|
m_match.SetPhase( GAMEPHASE_MATCH_ENDED );
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + mp_win_panel_display_time.GetInt();
|
|
GoToIntermission();
|
|
}
|
|
else if ( IsPlayingCoopMission() && iWinnerTeam == TEAM_CT )
|
|
{
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
CGameCoopMissionManager *pManager = GetCoopMissionManager();
|
|
// we really dont have one
|
|
if ( !pManager )
|
|
DevMsg( "Coop mission map is missing a game_coopmission_manager entity. You can't fire MissionCompleted outputs without it!\n" );
|
|
else
|
|
pManager->SetMissionCompleted();
|
|
}
|
|
|
|
// If human team wins then end the match right now!
|
|
m_match.SetPhase( GAMEPHASE_MATCH_ENDED );
|
|
m_phaseChangeAnnouncementTime = gpGlobals->curtime + 1.5;
|
|
GoToIntermission();
|
|
}
|
|
}
|
|
|
|
if ( iReason == Game_Commencing )
|
|
{
|
|
m_bWarmupPeriod = true;
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::CreateEndMatchMapGroupVoteOptions( void )
|
|
{
|
|
CUtlVector< int > arrVoteCandidates;
|
|
if ( g_pGameTypes )
|
|
{
|
|
const char* mapGroupName = gpGlobals->mapGroupName.ToCStr();
|
|
const CUtlStringList* mapsInGroup = g_pGameTypes->GetMapGroupMapList( mapGroupName );
|
|
|
|
if ( mapsInGroup )
|
|
{
|
|
int nCount = mapsInGroup->Count();
|
|
int nCurrentMapIndex = -1;
|
|
for ( int iMap = 0; iMap < nCount; ++ iMap )
|
|
{
|
|
arrVoteCandidates.AddToTail( iMap );
|
|
if ( !V_stricmp( mapsInGroup->Element( iMap ), STRING( gpGlobals->mapname ) ) )
|
|
nCurrentMapIndex = iMap;
|
|
}
|
|
|
|
// Remove current map from pool if mp_endmatch_votenextmap_keepcurrent is set to 0.
|
|
if ( !mp_endmatch_votenextmap_keepcurrent.GetBool() && nCurrentMapIndex >= 0 )
|
|
arrVoteCandidates.Remove( nCurrentMapIndex );
|
|
|
|
while ( arrVoteCandidates.Count() > 10 )
|
|
{
|
|
int nRemoveIndex = RandomInt( 0, arrVoteCandidates.Count() - 1 );
|
|
if ( mp_endmatch_votenextmap_keepcurrent.GetBool() && ( arrVoteCandidates[nRemoveIndex] == nCurrentMapIndex ) )
|
|
{
|
|
nRemoveIndex ++;
|
|
nRemoveIndex %= arrVoteCandidates.Count();
|
|
}
|
|
arrVoteCandidates.Remove( nRemoveIndex );
|
|
}
|
|
}
|
|
}
|
|
for ( int iVoteOption = 0; iVoteOption < MAX_ENDMATCH_VOTE_PANELS; ++ iVoteOption )
|
|
m_nEndMatchMapGroupVoteOptions.Set( iVoteOption, arrVoteCandidates.IsValidIndex( iVoteOption ) ? arrVoteCandidates[iVoteOption] : -1 );
|
|
}
|
|
|
|
void CCSGameRules::ReportRoundEndStatsToGC( CMsgGCCStrike15_v2_MatchmakingServerRoundStats **ppAllocateStats )
|
|
{
|
|
/** Removed for partner depot **/
|
|
}
|
|
|
|
// Helper to determine if all players on a team are playing for the same clan
|
|
bool CCSGameRules::IsClanTeam( CTeam *pTeam )
|
|
{
|
|
uint32 iTeamClan = 0;
|
|
bool bTeamInitialized = false;
|
|
|
|
for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
|
|
{
|
|
CBasePlayer *pPlayer = pTeam->GetPlayer( iPlayer );
|
|
if ( !pPlayer )
|
|
return false;
|
|
|
|
if ( pPlayer->IsBot() )
|
|
continue;
|
|
|
|
const char *pClanID = engine->GetClientConVarValue( pPlayer->entindex(), "cl_clanid" );
|
|
uint32 iPlayerClan = atoi( pClanID );
|
|
|
|
// Initialize the team clan
|
|
if ( !bTeamInitialized )
|
|
{
|
|
iTeamClan = iPlayerClan;
|
|
bTeamInitialized = true;
|
|
}
|
|
|
|
if ( iPlayerClan != iTeamClan || iPlayerClan == 0 )
|
|
return false;
|
|
|
|
}
|
|
return iTeamClan != 0;
|
|
}
|
|
void CCSGameRules::ModifyRealtimeBotDifficulty( CCSPlayer* pOnlyBotToProcess /* = NULL */ )
|
|
{
|
|
if ( !sv_auto_adjust_bot_difficulty.GetBool() )
|
|
return;
|
|
|
|
float fAvgPlayerCS = CalculateAveragePlayerContributionScore();
|
|
float fAvgBotCS;
|
|
|
|
if ( !sv_compute_per_bot_difficulty.GetBool() )
|
|
{
|
|
fAvgBotCS = CalculateAverageBotContributionScore();
|
|
}
|
|
else
|
|
{
|
|
fAvgBotCS = 0.0f;
|
|
}
|
|
|
|
float fLowWindowExtent = fAvgPlayerCS + bot_autodifficulty_threshold_low.GetFloat();
|
|
float fHighWindowExtent = fAvgPlayerCS + bot_autodifficulty_threshold_high.GetFloat();
|
|
|
|
// Ensure the high/low window extents are accurate
|
|
if ( fLowWindowExtent > fHighWindowExtent )
|
|
{
|
|
float swapval = fLowWindowExtent;
|
|
fLowWindowExtent = fHighWindowExtent;
|
|
fHighWindowExtent = swapval;
|
|
}
|
|
|
|
int nextBotDifficulty = -1;
|
|
|
|
if ( sv_compute_per_bot_difficulty.GetBool() )
|
|
{
|
|
// Compare incoming target contribution score to levels of difficulty, and choose a level for each bot
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( pOnlyBotToProcess && pPlayer != pOnlyBotToProcess )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( pPlayer->IsBot() )
|
|
{
|
|
CCSBot* pBot = dynamic_cast< CCSBot* >( pPlayer );
|
|
|
|
if ( pBot )
|
|
{
|
|
const BotProfile* pProfile = pBot->GetProfile();
|
|
|
|
if ( pProfile && TheBotProfiles )
|
|
{
|
|
float cScore = ( float )pPlayer->GetContributionScore();
|
|
|
|
nextBotDifficulty = -1;
|
|
DevMsg( "+++++Bot %s has CS = %f. Low = %f, High = %f\n", pPlayer->GetPlayerName(), cScore, fLowWindowExtent, fHighWindowExtent );
|
|
|
|
if ( cScore < fLowWindowExtent )
|
|
{
|
|
// Bot should have higher difficulty
|
|
DevMsg( "+++++Bot %s with CS = %f, should increase its difficulty\n", pPlayer->GetPlayerName(), cScore );
|
|
|
|
nextBotDifficulty = pProfile->GetMaxDifficulty() + 1;
|
|
|
|
// Ensure new difficulty level is valid
|
|
if ( nextBotDifficulty >= NUM_DIFFICULTY_LEVELS )
|
|
{
|
|
nextBotDifficulty = NUM_DIFFICULTY_LEVELS - 1;
|
|
}
|
|
}
|
|
else if ( cScore > fHighWindowExtent )
|
|
{
|
|
// Bot should have lower difficulty
|
|
DevMsg( "+++++Bot %s with CS = %f, should decrease its difficulty\n", pPlayer->GetPlayerName(), cScore );
|
|
|
|
nextBotDifficulty = pProfile->GetMaxDifficulty() - 1;
|
|
|
|
// Ensure new difficulty level is valid
|
|
if ( nextBotDifficulty < BOT_EASY )
|
|
{
|
|
nextBotDifficulty = BOT_EASY;
|
|
}
|
|
}
|
|
|
|
// In queue matchmaking mode bots are always obeying bot manager difficulty
|
|
if ( IsQueuedMatchmaking() )
|
|
nextBotDifficulty = CCSBotManager::GetDifficultyLevel();
|
|
|
|
if ( nextBotDifficulty >= BOT_EASY )
|
|
{
|
|
// Change the bot's new profile based on desired difficulty
|
|
const BotProfile* pNewProfileData = TheBotProfiles->GetRandomProfile( ( BotDifficultyType )nextBotDifficulty, pBot->GetTeamNumber(), WEAPONTYPE_UNKNOWN, true );
|
|
|
|
if ( NULL == pNewProfileData )
|
|
{
|
|
Warning( "-----No profile found to match search criteria. Not updating this bot's difficulty.");
|
|
}
|
|
else
|
|
{
|
|
// Change the parameters of the bot's profile to match the new difficulty settings
|
|
DevMsg( "+++++Bot %s with Max Difficulty %d will become Bot %s with Max Difficulty %d\n", pProfile->GetName(), pProfile->GetMaxDifficulty(), pNewProfileData->GetName(), pNewProfileData->GetMaxDifficulty() );
|
|
pBot->Initialize( pNewProfileData, pBot->GetTeamNumber() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handle the alternate case for all bots changing difficulty as a group (all bots will have identical difficulty)
|
|
// DevMsg( "+++++Average Bot Contribution Score = %f\n", fAvgBotCS );
|
|
|
|
if ( fAvgBotCS < fLowWindowExtent )
|
|
{
|
|
// Bots should have higher difficulty
|
|
nextBotDifficulty = -1;
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
|
|
|
|
if ( pOnlyBotToProcess && pPlayer != pOnlyBotToProcess )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( pPlayer && pPlayer->IsBot() )
|
|
{
|
|
CCSBot* pBot = dynamic_cast< CCSBot* >( pPlayer );
|
|
|
|
if ( pBot )
|
|
{
|
|
const BotProfile* pProfile = pBot->GetProfile();
|
|
|
|
if ( pProfile && TheBotProfiles && nextBotDifficulty == -1 )
|
|
{
|
|
nextBotDifficulty = pProfile->GetMaxDifficulty() + 1;
|
|
|
|
if ( nextBotDifficulty > BOT_EXPERT )
|
|
{
|
|
nextBotDifficulty = BOT_EXPERT;
|
|
}
|
|
}
|
|
|
|
// In queue matchmaking mode bots are always obeying bot manager difficulty
|
|
if ( IsQueuedMatchmaking() )
|
|
nextBotDifficulty = CCSBotManager::GetDifficultyLevel();
|
|
|
|
if ( nextBotDifficulty > -1 )
|
|
{
|
|
// Have a valid difficulty level, so apply it to each bot
|
|
const BotProfile* pNewProfileData = TheBotProfiles->GetRandomProfile( ( BotDifficultyType )nextBotDifficulty, pBot->GetTeamNumber(), WEAPONTYPE_UNKNOWN, true );
|
|
|
|
if ( NULL == pNewProfileData )
|
|
{
|
|
Warning( "-----No profile found to match search criteria. Not updating this bot's difficulty.");
|
|
}
|
|
else
|
|
{
|
|
// Change the parameters of the bot's profile to match the new difficulty settings
|
|
DevMsg( "+++++Bot %s with Max Difficulty %d will become Bot %s with Max Difficulty %d\n", pProfile->GetName(), pProfile->GetMaxDifficulty(), pNewProfileData->GetName(), pNewProfileData->GetMaxDifficulty() );
|
|
pBot->Initialize( pNewProfileData, pBot->GetTeamNumber() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( fAvgBotCS > fHighWindowExtent )
|
|
{
|
|
// Bots should have lower difficulty
|
|
nextBotDifficulty = -1;
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
|
|
|
|
if ( pOnlyBotToProcess && pPlayer != pOnlyBotToProcess )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( pPlayer && pPlayer->IsBot() )
|
|
{
|
|
CCSBot* pBot = dynamic_cast< CCSBot* >( pPlayer );
|
|
|
|
if ( pBot )
|
|
{
|
|
const BotProfile* pProfile = pBot->GetProfile();
|
|
|
|
if ( pProfile && TheBotProfiles && nextBotDifficulty == -1 )
|
|
{
|
|
nextBotDifficulty = pProfile->GetMaxDifficulty() - 1;
|
|
|
|
// Ensure new difficulty level is valid
|
|
if ( nextBotDifficulty < BOT_EASY )
|
|
{
|
|
nextBotDifficulty = BOT_EASY;
|
|
}
|
|
}
|
|
|
|
// In queue matchmaking mode bots are always obeying bot manager difficulty
|
|
if ( IsQueuedMatchmaking() )
|
|
nextBotDifficulty = CCSBotManager::GetDifficultyLevel();
|
|
|
|
if ( nextBotDifficulty > -1 && ( pProfile->GetMaxDifficulty() != nextBotDifficulty ) )
|
|
{
|
|
// Have a valid difficulty level, so apply it to each bot
|
|
const BotProfile* pNewProfileData = TheBotProfiles->GetRandomProfile( ( BotDifficultyType )nextBotDifficulty, pBot->GetTeamNumber(), WEAPONTYPE_UNKNOWN, true );
|
|
|
|
if ( NULL == pNewProfileData )
|
|
{
|
|
Warning( "-----No profile found to match search criteria. Not updating this bot's difficulty.");
|
|
}
|
|
else
|
|
{
|
|
// Change the parameters of the bot's profile to match the new difficulty settings
|
|
DevMsg( "+++++Bot %s with Max Difficulty %d will become Bot %s with Max Difficulty %d\n", pProfile->GetName(), pProfile->GetMaxDifficulty( ), pNewProfileData->GetName(), pNewProfileData->GetMaxDifficulty() );
|
|
pBot->Initialize( pNewProfileData, pBot->GetTeamNumber() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !IsPlayingOffline() )
|
|
{
|
|
fAvgBotCS = CalculateAverageBotContributionScore();
|
|
//DevMsg( "Average Bot Difficulty = %f\n", fAvgBotCS );
|
|
|
|
// Store max bot difficulty in the convar representing the player's input device
|
|
sv_bot_difficulty_kbm.SetValue( fAvgBotCS );
|
|
}
|
|
}
|
|
|
|
float CCSGameRules::CalculateAveragePlayerContributionScore( void )
|
|
{
|
|
// Loop through all players and get average human contribution score
|
|
int cscoreTotal = 0;
|
|
int numHumanPlayers = 0;
|
|
float avgHumanContributionScore = 0.0f;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !pPlayer->IsBot() )
|
|
{
|
|
cscoreTotal += pPlayer->GetContributionScore();
|
|
numHumanPlayers++;
|
|
}
|
|
}
|
|
|
|
if ( numHumanPlayers > 0 )
|
|
{
|
|
avgHumanContributionScore = ( float )cscoreTotal / ( float )numHumanPlayers;
|
|
}
|
|
|
|
return avgHumanContributionScore;
|
|
}
|
|
|
|
float CCSGameRules::CalculateAverageBotContributionScore( void )
|
|
{
|
|
// Loop through all players and get average bot contribution score
|
|
int cscoreTotal = 0;
|
|
int numBots = 0;
|
|
float avgBotContributionScore = 0.0f;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ( CCSPlayer* )UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( pPlayer->IsBot() )
|
|
{
|
|
cscoreTotal += pPlayer->GetContributionScore();
|
|
numBots++;
|
|
}
|
|
}
|
|
|
|
if ( numBots > 0 )
|
|
{
|
|
avgBotContributionScore = ( float )cscoreTotal / ( float )numBots;
|
|
}
|
|
|
|
return avgBotContributionScore;
|
|
}
|
|
|
|
// [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)
|
|
{
|
|
if( IsPlayingGunGameProgressive() )
|
|
UpdateMatchStats(pPlayer,iWinnerTeam);
|
|
|
|
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
|
|
&& !CSGameRules()->IsPlayingGunGameProgressive() )
|
|
{
|
|
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 == TEAM_CT && 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//[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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::CheckMapConditions()
|
|
{
|
|
// Check to see if this map has a bomb target in it
|
|
if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) )
|
|
{
|
|
// this is a bit hacky, but it makes it so the bomb stuff only shows up on mission 3 of the coop mission
|
|
if ( IsPlayingCoopMission() && mp_anyone_can_pickup_c4.GetBool() == false )
|
|
{
|
|
m_bMapHasBombTarget = false;
|
|
m_bMapHasBombZone = false;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
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(); */
|
|
}
|
|
|
|
m_match.SwapTeamScores();
|
|
}
|
|
|
|
// reset player scores, team scores, and player controls, restart round; useful for running a demo
|
|
void CCSGameRules::ResetForTradeshow( void )
|
|
{
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = CCSPlayer::Instance( i );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->Reset( true );
|
|
}
|
|
}
|
|
|
|
m_match.Reset();
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "reset_player_controls" );
|
|
if ( event )
|
|
{
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
ClearGunGameData();
|
|
|
|
EndRound();
|
|
}
|
|
|
|
|
|
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.
|
|
|
|
// first go through the current players and see if they are parented to something
|
|
// if so, unparent them before we might remove their parent and cause a crash
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = CCSPlayer::Instance( i );
|
|
|
|
if ( pPlayer && !FNullEnt( pPlayer->edict() ) )
|
|
{
|
|
if ( pPlayer->GetParent() )
|
|
pPlayer->SetParent( NULL );
|
|
}
|
|
}
|
|
|
|
// Get rid of all entities except players.
|
|
CBaseEntity *pCur = gEntList.FirstEnt();
|
|
while ( pCur )
|
|
{
|
|
CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pCur );
|
|
CBombTarget *pBombTarg = dynamic_cast< CBombTarget* >( pCur );
|
|
CHostageRescueZone *pRescueZone = dynamic_cast< CHostageRescueZone* >( pCur );
|
|
|
|
// Weapons with owners don't want to be removed..
|
|
if ( pWeapon )
|
|
{
|
|
// [dwenger] Handle round restart processing for the weapon.
|
|
pWeapon->OnRoundRestart();
|
|
|
|
if ( pWeapon->ShouldRemoveOnRoundRestart() )
|
|
{
|
|
UTIL_Remove( pCur );
|
|
}
|
|
}
|
|
// bomb targets have a re-init function to call
|
|
else if ( pBombTarg )
|
|
{
|
|
pBombTarg->ReInitOnRoundStart();
|
|
}
|
|
else if ( pRescueZone )
|
|
{
|
|
pRescueZone->ReInitOnRoundStart();
|
|
}
|
|
// remove entities that has to be restored on roundrestart (breakables etc)
|
|
else if ( !CS_FindInList( s_PreserveEnts, pCur->GetClassname() ) )
|
|
{
|
|
if( !Commentary_IsCommentaryEntity( pCur ) ) //leave commentary alone
|
|
{
|
|
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 || INDEXENT( 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 );
|
|
|
|
|
|
// this kind of sucks. on ps3, we need to delete all physics props that are not set to debris to fix a crash,
|
|
// but we don't know if the prop has the debris flag until after it's been removed and then recreated again.
|
|
// that's why we have to loop through all of the entities again here.
|
|
if ( IsPS3() || engine->IsDedicatedServerForPS3() )
|
|
{
|
|
int nNumPhysPropsDeleted = 0;
|
|
CBaseEntity *pPhysCur = gEntList.FirstEnt();
|
|
while ( pPhysCur )
|
|
{
|
|
CPhysicsProp *pPhysProp = dynamic_cast< CPhysicsProp* >( pPhysCur );
|
|
if ( pPhysProp && !pPhysProp->HasSpawnFlags( SF_PHYSPROP_DEBRIS ) )
|
|
{
|
|
UTIL_Remove( pPhysCur );
|
|
nNumPhysPropsDeleted++;
|
|
}
|
|
|
|
pPhysCur = gEntList.NextEnt( pPhysCur );
|
|
}
|
|
|
|
if ( nNumPhysPropsDeleted > 0 )
|
|
Msg( "DELETED %d prop_physics or prop_physics_multiplayer that were not set to debris!!\n", nNumPhysPropsDeleted );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
CCSPlayer *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 pPlayer; //There you are.
|
|
}
|
|
}
|
|
|
|
//Didn't find a bomber.
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void CCSGameRules::EndRound()
|
|
{
|
|
// fake a round end
|
|
CSGameRules()->TerminateRound( 0.0f, Round_Draw );
|
|
}
|
|
|
|
CBaseEntity *CCSGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer )
|
|
{
|
|
// we don't want to get a spawn if we are already in the process of spawning with one because
|
|
// it increments the spawn number without actually spawning and messes up the spawn priority
|
|
CCSPlayer* pCSPlayer = ToCSPlayer( pPlayer );
|
|
if ( !pCSPlayer || pCSPlayer->IsPlayerSpawning() )
|
|
return NULL;
|
|
|
|
pCSPlayer->SetPlayerSpawning( true );
|
|
|
|
// get valid spawn point
|
|
CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint();
|
|
|
|
if ( pPlayer->IsBot() && pPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
// // disable the spawn after someone spawn here in coop
|
|
SpawnPointCoopEnemy *pEnemySpawnSpot = dynamic_cast< SpawnPointCoopEnemy* >( pSpawnSpot );
|
|
if ( pEnemySpawnSpot )
|
|
{
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
CCSBot* pBot = static_cast< CCSBot* >( pPlayer );
|
|
if ( pBot )
|
|
{
|
|
pBot->SetLastCoopSpawnPoint( pEnemySpawnSpot );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// drop down to ground
|
|
Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin() + Vector( 0, 0, 16 ), VEC_HULL_MIN + Vector( -4, -4, 0 ), VEC_HULL_MAX + Vector( 4, 4, 0 ) );
|
|
|
|
// Move the player to the place it said.
|
|
pPlayer->Teleport( &GroundPos, &pSpawnSpot->GetLocalAngles(), &vec3_origin );
|
|
pPlayer->m_Local.m_viewPunchAngle = vec3_angle;
|
|
return pSpawnSpot;
|
|
}
|
|
|
|
// checks if the spot is clear of players
|
|
bool CCSGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer )
|
|
{
|
|
if ( !pSpot )
|
|
return false;
|
|
|
|
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.
|
|
CTraceFilterSimple traceFilter( pPlayer, COLLISION_GROUP_PLAYER );
|
|
if ( !UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs, MASK_SOLID, &traceFilter ) )
|
|
return false;
|
|
|
|
// Test against other players potentially occupying this spot
|
|
for ( int k = 1; k <= gpGlobals->maxClients; ++ k )
|
|
{
|
|
CBasePlayer *pOther = UTIL_PlayerByIndex( k );
|
|
if ( !pOther ) continue;
|
|
if ( pOther == pPlayer ) continue;
|
|
if ( ( pOther->GetTeamNumber() != TEAM_TERRORIST )
|
|
&& ( pOther->GetTeamNumber() != TEAM_CT ) )
|
|
continue;
|
|
|
|
if ( ( pOther->GetAbsOrigin().AsVector2D() - pSpot->GetAbsOrigin().AsVector2D() ).IsZero() )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCSGameRules::IsSpawnPointHiddenFromOtherPlayers( CBaseEntity *pSpot, CBasePlayer *pPlayer, int nHideFromTeam )
|
|
{
|
|
Vector vecSpot = pSpot->GetAbsOrigin() + Vector( 0, 0, 32 );
|
|
if ( nHideFromTeam > 0 )
|
|
{
|
|
if ( nHideFromTeam == TEAM_CT && UTIL_IsVisibleToTeam( vecSpot, TEAM_CT ) )
|
|
return false;
|
|
else if ( nHideFromTeam == TEAM_TERRORIST && UTIL_IsVisibleToTeam( vecSpot, TEAM_TERRORIST ) )
|
|
return false;
|
|
}
|
|
else if ( nHideFromTeam == 0 && ( UTIL_IsVisibleToTeam( vecSpot, TEAM_CT ) ) ||
|
|
( UTIL_IsVisibleToTeam( vecSpot, TEAM_TERRORIST ) ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
m_bHasHostageBeenTouched = true;
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
CTsReachedHostageRoundEndCheck();
|
|
}
|
|
|
|
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 );
|
|
|
|
g_voteControllerGlobal = static_cast< CVoteController *>( CBaseEntity::Create("vote_controller", vec3_origin, vec3_angle) );
|
|
g_voteControllerCT = static_cast< CVoteController *>( CBaseEntity::Create("vote_controller", vec3_origin, vec3_angle) );
|
|
g_voteControllerT = static_cast< CVoteController *>( CBaseEntity::Create("vote_controller", vec3_origin, vec3_angle) );
|
|
// Vote Issue classes are handled/cleaned-up by g_voteControllers
|
|
|
|
NewTeamIssue< CKickIssue >();
|
|
NewGlobalIssue< CRestartGameIssue >();
|
|
NewGlobalIssue< CChangeLevelIssue >();
|
|
NewGlobalIssue< CNextLevelIssue >();
|
|
|
|
if ( IsPlayingAnyCompetitiveStrictRuleset() )
|
|
{
|
|
NewTeamIssue< CStartTimeOutIssue >();
|
|
}
|
|
|
|
static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
|
|
if ( s_pchTournamentServer && IsQueuedMatchmaking() )
|
|
{
|
|
NewGlobalIssue< CPauseMatchIssue >();
|
|
NewGlobalIssue< CUnpauseMatchIssue >();
|
|
NewGlobalIssue< CLoadBackupIssue >();
|
|
NewGlobalIssue< CReadyForMatchIssue >();
|
|
NewGlobalIssue< CNotReadyForMatchIssue >();
|
|
}
|
|
|
|
if ( IsQueuedMatchmaking() )
|
|
{
|
|
// new CQueuedMatchmakingRematch;
|
|
// new CQueuedMatchmakingContinue;
|
|
NewTeamIssue< CSurrender >();
|
|
}
|
|
else
|
|
{
|
|
NewGlobalIssue< CScrambleTeams >();
|
|
NewGlobalIssue< CSwapTeams >();
|
|
// new CSurrender;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endif // CLIENT_DLL
|
|
|
|
ConVar cash_team_terrorist_win_bomb(
|
|
"cash_team_terrorist_win_bomb",
|
|
"3500",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_elimination_hostage_map_t(
|
|
"cash_team_elimination_hostage_map_t",
|
|
"1000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_elimination_hostage_map_ct(
|
|
"cash_team_elimination_hostage_map_ct",
|
|
"2000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_elimination_bomb_map(
|
|
"cash_team_elimination_bomb_map",
|
|
"3250",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_survive_guardian_wave(
|
|
"cash_team_survive_guardian_wave",
|
|
"1000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY );
|
|
|
|
ConVar cash_team_win_by_time_running_out_hostage(
|
|
"cash_team_win_by_time_running_out_hostage",
|
|
"3250",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_win_by_time_running_out_bomb(
|
|
"cash_team_win_by_time_running_out_bomb",
|
|
"3250",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_win_by_defusing_bomb(
|
|
"cash_team_win_by_defusing_bomb",
|
|
"3250",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_win_by_hostage_rescue(
|
|
"cash_team_win_by_hostage_rescue",
|
|
"3500",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_loser_bonus(
|
|
"cash_team_loser_bonus",
|
|
"1400",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_loser_bonus_consecutive_rounds(
|
|
"cash_team_loser_bonus_consecutive_rounds",
|
|
"500",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_rescued_hostage(
|
|
"cash_team_rescued_hostage",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_hostage_alive(
|
|
"cash_team_hostage_alive",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_planted_bomb_but_defused(
|
|
"cash_team_planted_bomb_but_defused",
|
|
"800",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_team_hostage_interaction(
|
|
"cash_team_hostage_interaction",
|
|
"500",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_killed_teammate(
|
|
"cash_player_killed_teammate",
|
|
"-300",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_killed_enemy_factor(
|
|
"cash_player_killed_enemy_factor",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_killed_enemy_default(
|
|
"cash_player_killed_enemy_default",
|
|
"300",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_bomb_planted(
|
|
"cash_player_bomb_planted",
|
|
"300",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_bomb_defused(
|
|
"cash_player_bomb_defused",
|
|
"300",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_rescued_hostage(
|
|
"cash_player_rescued_hostage",
|
|
"1000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_interact_with_hostage(
|
|
"cash_player_interact_with_hostage",
|
|
"150",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_damage_hostage(
|
|
"cash_player_damage_hostage",
|
|
"-30",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_killed_hostage(
|
|
"cash_player_killed_hostage",
|
|
"-1000",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_respawn_amount(
|
|
"cash_player_respawn_amount",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
ConVar cash_player_get_killed(
|
|
"cash_player_get_killed",
|
|
"0",
|
|
FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_NOTIFY);
|
|
|
|
|
|
int CCSGameRules::TeamCashAwardValue( TeamCashAward::Type reason)
|
|
{
|
|
switch( reason )
|
|
{
|
|
case TeamCashAward::TERRORIST_WIN_BOMB: return cash_team_terrorist_win_bomb.GetInt();
|
|
case TeamCashAward::ELIMINATION_HOSTAGE_MAP_T: return cash_team_elimination_hostage_map_t.GetInt();
|
|
case TeamCashAward::ELIMINATION_HOSTAGE_MAP_CT: return cash_team_elimination_hostage_map_ct.GetInt();
|
|
case TeamCashAward::ELIMINATION_BOMB_MAP: return cash_team_elimination_bomb_map.GetInt();
|
|
case TeamCashAward::WIN_BY_TIME_RUNNING_OUT_HOSTAGE:return cash_team_win_by_time_running_out_hostage.GetInt();
|
|
case TeamCashAward::WIN_BY_TIME_RUNNING_OUT_BOMB: return cash_team_win_by_time_running_out_bomb.GetInt();
|
|
case TeamCashAward::WIN_BY_DEFUSING_BOMB: return cash_team_win_by_defusing_bomb.GetInt();
|
|
case TeamCashAward::WIN_BY_HOSTAGE_RESCUE: return cash_team_win_by_hostage_rescue.GetInt();
|
|
case TeamCashAward::LOSER_BONUS: return cash_team_loser_bonus.GetInt();
|
|
case TeamCashAward::LOSER_BONUS_CONSECUTIVE_ROUNDS: return cash_team_loser_bonus_consecutive_rounds.GetInt();
|
|
case TeamCashAward::RESCUED_HOSTAGE: return cash_team_rescued_hostage.GetInt();
|
|
case TeamCashAward::HOSTAGE_ALIVE: return cash_team_hostage_alive.GetInt();
|
|
case TeamCashAward::PLANTED_BOMB_BUT_DEFUSED: return cash_team_planted_bomb_but_defused.GetInt();
|
|
case TeamCashAward::HOSTAGE_INTERACTION: return cash_team_hostage_interaction.GetInt();
|
|
case TeamCashAward::SURVIVE_GUARDIAN_WAVE: return cash_team_survive_guardian_wave.GetInt();
|
|
|
|
default:
|
|
AssertMsg( false, "Unhandled TeamCashAwardReason" );
|
|
return 0;
|
|
};
|
|
}
|
|
|
|
int CCSGameRules::PlayerCashAwardValue( PlayerCashAward::Type reason)
|
|
{
|
|
switch( reason )
|
|
{
|
|
case PlayerCashAward::NONE: return 0;
|
|
case PlayerCashAward::KILL_TEAMMATE: return cash_player_killed_teammate.GetInt();
|
|
case PlayerCashAward::KILLED_ENEMY: return cash_player_killed_enemy_default.GetInt();
|
|
case PlayerCashAward::BOMB_PLANTED: return cash_player_bomb_planted.GetInt();
|
|
case PlayerCashAward::BOMB_DEFUSED: return cash_player_bomb_defused.GetInt();
|
|
case PlayerCashAward::RESCUED_HOSTAGE: return cash_player_rescued_hostage.GetInt();
|
|
case PlayerCashAward::INTERACT_WITH_HOSTAGE: return cash_player_interact_with_hostage.GetInt();
|
|
case PlayerCashAward::DAMAGE_HOSTAGE: return cash_player_damage_hostage.GetInt();
|
|
case PlayerCashAward::KILL_HOSTAGE: return cash_player_killed_hostage.GetInt();
|
|
case PlayerCashAward::RESPAWN: return cash_player_respawn_amount.GetInt();
|
|
case PlayerCashAward::GET_KILLED: return cash_player_get_killed.GetInt();
|
|
default:
|
|
AssertMsg( false, "Unhandled PlayerCashAwardReason" );
|
|
return 0;
|
|
};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Guardian mode getter functions for UI
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CCSGameRules::GetGuardianRequiredKills() const
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
static ConVarRef mp_guardian_special_kills_needed( "mp_guardian_special_kills_needed" );
|
|
#endif
|
|
|
|
return mp_guardian_special_kills_needed.GetInt();
|
|
}
|
|
|
|
int CCSGameRules::GetGuardianKillsRemaining() const
|
|
{
|
|
return m_nGuardianModeSpecialKillsRemaining;
|
|
}
|
|
|
|
int CCSGameRules::GetGuardianSpecialWeapon() const
|
|
{
|
|
return m_nGuardianModeSpecialWeaponNeeded;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Gun game getter functions
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Determines the highest weapon index of all players in the arms race match
|
|
void CCSGameRules::CalculateMaxGunGameProgressiveWeaponIndex( void )
|
|
{
|
|
m_iMaxGunGameProgressiveWeaponIndex = 0;
|
|
|
|
if ( IsPlayingGunGameProgressive() )
|
|
{
|
|
// Loop through all players and find the max progressive weapon index
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer* pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
if ( pPlayer->GetPlayerGunGameWeaponIndex() > m_iMaxGunGameProgressiveWeaponIndex )
|
|
{
|
|
m_iMaxGunGameProgressiveWeaponIndex = pPlayer->GetPlayerGunGameWeaponIndex();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int CCSGameRules::GetCurrentGunGameWeapon ( int nCurrentWeaponIndex, int nTeamID )
|
|
{
|
|
if ( GetNumProgressiveGunGameWeapons( nTeamID ) == 0 )
|
|
{
|
|
// Don't process if weapon array is empty
|
|
return -1;
|
|
}
|
|
|
|
if ( nCurrentWeaponIndex < 0 || nCurrentWeaponIndex >= GetNumProgressiveGunGameWeapons( nTeamID ) )
|
|
{
|
|
// Don't process if out of the array bounds
|
|
return -1;
|
|
}
|
|
|
|
return GetProgressiveGunGameWeapon( nCurrentWeaponIndex, nTeamID );
|
|
}
|
|
|
|
int CCSGameRules::GetNextGunGameWeapon( int nCurrentWeaponIndex, int nTeamID )
|
|
{
|
|
if ( GetNumProgressiveGunGameWeapons( nTeamID ) == 0 )
|
|
{
|
|
// Don't process if weapon array is empty
|
|
return -1;
|
|
}
|
|
|
|
if ( nCurrentWeaponIndex < 0 || nCurrentWeaponIndex >= GetNumProgressiveGunGameWeapons( nTeamID ) - 1 )
|
|
{
|
|
// Don't process if already at last weapon or out of the array bounds
|
|
return -1;
|
|
}
|
|
|
|
return GetProgressiveGunGameWeapon( nCurrentWeaponIndex + 1, nTeamID );
|
|
}
|
|
|
|
int CCSGameRules::GetPreviousGunGameWeapon( int nCurrentWeaponIndex, int nTeamID )
|
|
{
|
|
if ( GetNumProgressiveGunGameWeapons( nTeamID ) == 0 )
|
|
{
|
|
// Don't process if weapon array is empty
|
|
return -1;
|
|
}
|
|
|
|
if ( nCurrentWeaponIndex <= 0 || nCurrentWeaponIndex >= GetNumProgressiveGunGameWeapons( nTeamID ) )
|
|
{
|
|
// Don't process if already at first weapon or out of the array bounds
|
|
return -1;
|
|
}
|
|
|
|
return GetProgressiveGunGameWeapon( nCurrentWeaponIndex - 1, nTeamID );
|
|
}
|
|
|
|
bool CCSGameRules::IsFinalGunGameProgressiveWeapon( int nCurrentWeaponIndex, int nTeamID )
|
|
{
|
|
// Determine if the current weapon is the last in the list of gun game weapons
|
|
if ( nCurrentWeaponIndex == GetNumProgressiveGunGameWeapons( nTeamID ) - 1 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int CCSGameRules::GetGunGameNumKillsRequiredForWeapon( int nCurrentWeaponIndex, int nTeamID )
|
|
{
|
|
if ( nCurrentWeaponIndex < 0 || nCurrentWeaponIndex > GetNumProgressiveGunGameWeapons( nTeamID ) - 1 )
|
|
{
|
|
// Don't process if out of the array bounds
|
|
return -1;
|
|
}
|
|
|
|
return GetProgressiveGunGameWeaponKillRequirement( nCurrentWeaponIndex, nTeamID );
|
|
}
|
|
|
|
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() )
|
|
{
|
|
int nAmmo = 0;
|
|
if ( weapon->UsesClipsForAmmo1() )
|
|
{
|
|
nAmmo = weapon->Clip1();
|
|
}
|
|
else
|
|
{
|
|
if ( pPlayer )
|
|
{
|
|
nAmmo = weapon->GetReserveAmmoCount( AMMO_POSITION_PRIMARY );
|
|
}
|
|
else
|
|
{
|
|
// No owner, so return how much primary ammo I have along with me.
|
|
nAmmo = weapon->GetPrimaryAmmoCount();
|
|
}
|
|
}
|
|
|
|
if ( nAmmo > 0 )
|
|
{
|
|
bestWeapon = weapon;
|
|
}
|
|
}
|
|
else if ( weapon->GetSlot() == bestWeapon->GetSlot() && weapon->GetPosition() < bestWeapon->GetPosition() )
|
|
{
|
|
bestWeapon = weapon;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bestWeapon = weapon;
|
|
}
|
|
}
|
|
|
|
return bestWeapon;
|
|
}
|
|
|
|
float CCSGameRules::GetMapRemainingTime()
|
|
{
|
|
// 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() const
|
|
{
|
|
return (float) (m_fRoundStartTime + m_iRoundTime) - gpGlobals->curtime;
|
|
}
|
|
|
|
float CCSGameRules::GetRoundStartTime()
|
|
{
|
|
return m_fRoundStartTime;
|
|
}
|
|
|
|
|
|
float CCSGameRules::GetRoundElapsedTime()
|
|
{
|
|
if ( IsPlayingTraining() )
|
|
return 0.0f;
|
|
|
|
float remainingTime = m_iRoundTime - GetRoundRemainingTime();
|
|
if ( remainingTime >= 0.0f )
|
|
return remainingTime;
|
|
else
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
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::IsWarmupPeriod() const
|
|
{
|
|
if ( IsPlayingTraining() )
|
|
return false;
|
|
|
|
if ( IsPlayingOffline() && !mp_do_warmup_offine.GetBool() )
|
|
return false;
|
|
|
|
return m_bWarmupPeriod;
|
|
}
|
|
|
|
bool CCSGameRules::AllowTaunts( void )
|
|
{
|
|
/** Removed for partner depot **/
|
|
return false;
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
bool CCSGameRules::AllowThirdPersonCamera()
|
|
{
|
|
return sv_allow_thirdperson.GetBool();
|
|
}
|
|
|
|
bool CCSGameRules::IsGoodDownTime( void )
|
|
{
|
|
if ( CSGameRules()->IsGameRestarting() )
|
|
return true;
|
|
|
|
if ( CSGameRules()->IsIntermission() )
|
|
return true;
|
|
|
|
if ( CSGameRules()->InRoundRestart() )
|
|
return true;
|
|
|
|
if ( ( ( CSGameRules()->GetRoundRestartTime() - gpGlobals->curtime ) < 0.5f
|
|
&& ( CSGameRules()->GetRoundRestartTime() - gpGlobals->curtime ) > 0.0f ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCSGameRules::IsLoadoutAllowed( void )
|
|
{
|
|
// main menu
|
|
if ( !engine->IsConnected() )
|
|
return true;
|
|
|
|
C_CSPlayer *pLocalPlayer = C_CSPlayer::GetLocalCSPlayer();
|
|
|
|
if ( !pLocalPlayer )
|
|
return false;
|
|
|
|
// non players
|
|
if ( ( pLocalPlayer->IsSpectator() ) ||
|
|
( pLocalPlayer->IsHLTV() ) ||
|
|
( engine->IsPlayingDemo() ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// game mode specific rules
|
|
if ( IsPlayingAnyCompetitiveStrictRuleset() )
|
|
{
|
|
return ( IsWarmupPeriod() );
|
|
}
|
|
else if ( ( IsPlayingGunGameDeathmatch() || IsPlayingCooperativeGametype() ) )
|
|
{
|
|
return true;
|
|
}
|
|
else if ( IsWarmupPeriod() || !pLocalPlayer->IsAlive() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
|
|
float CCSGameRules::GetWarmupPeriodEndTime() const
|
|
{
|
|
return m_fWarmupPeriodStart + mp_warmuptime.GetFloat();
|
|
}
|
|
|
|
bool CCSGameRules::IsWarmupPeriodPaused()
|
|
{
|
|
return mp_warmup_pausetimer.GetBool();
|
|
}
|
|
|
|
bool CCSGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
if ( pPlayer )
|
|
{
|
|
int iPlayerTeam = pPlayer->GetTeamNumber();
|
|
if ( ( iPlayerTeam == TEAM_TERRORIST ) || ( iPlayerTeam == TEAM_CT ) )
|
|
return false;
|
|
}
|
|
#else
|
|
int iLocalPlayerTeam = GetLocalPlayerTeam();
|
|
if ( ( iLocalPlayerTeam == TEAM_TERRORIST ) || ( iLocalPlayerTeam == TEAM_CT ) )
|
|
return false;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
void CCSGameRules::StartWarmup( void )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
m_bWarmupPeriod = true;
|
|
m_bCompleteReset = true;
|
|
m_fWarmupPeriodStart = gpGlobals->curtime;
|
|
|
|
RestartRound();
|
|
}
|
|
|
|
void CCSGameRules::EndWarmup( void )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
if ( !m_bWarmupPeriod )
|
|
return;
|
|
|
|
m_bWarmupPeriod = false;
|
|
m_bCompleteReset = true;
|
|
m_fWarmupPeriodStart = -1;
|
|
|
|
CCSPlayerResource *pResource = dynamic_cast< CCSPlayerResource * >( g_pPlayerResource );
|
|
if ( pResource )
|
|
pResource->ForcePlayersPickColors();
|
|
|
|
RestartRound();
|
|
}
|
|
|
|
void CCSGameRules::StartTerroristTimeOut( void )
|
|
{
|
|
if ( m_bTerroristTimeOutActive || m_bCTTimeOutActive )
|
|
return;
|
|
|
|
if ( m_nTerroristTimeOuts <= 0 )
|
|
return;
|
|
|
|
m_bTerroristTimeOutActive = true;
|
|
m_flTerroristTimeOutRemaining = mp_team_timeout_time.GetInt();
|
|
m_nTerroristTimeOuts--;
|
|
m_bMatchWaitingForResume = true;
|
|
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Match_Will_Pause" );
|
|
}
|
|
|
|
void CCSGameRules::EndTerroristTimeOut( void )
|
|
{
|
|
if ( !m_bTerroristTimeOutActive )
|
|
return;
|
|
|
|
m_bTerroristTimeOutActive = false;
|
|
m_bMatchWaitingForResume = false;
|
|
}
|
|
|
|
void CCSGameRules::StartCTTimeOut( void )
|
|
{
|
|
if ( m_bCTTimeOutActive || m_bTerroristTimeOutActive )
|
|
return;
|
|
|
|
if ( m_nCTTimeOuts <= 0 )
|
|
return;
|
|
|
|
m_bCTTimeOutActive = true;
|
|
m_flCTTimeOutRemaining = mp_team_timeout_time.GetInt();
|
|
m_nCTTimeOuts--;
|
|
m_bMatchWaitingForResume = true;
|
|
|
|
|
|
UTIL_ClientPrintAll( HUD_PRINTCENTER, "#SFUI_Notice_Match_Will_Pause" );
|
|
}
|
|
|
|
void CCSGameRules::EndCTTimeOut( void )
|
|
{
|
|
if ( !m_bCTTimeOutActive )
|
|
return;
|
|
|
|
m_bCTTimeOutActive = false;
|
|
m_bMatchWaitingForResume = false;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
AcquireResult::Type CCSGameRules::IsWeaponAllowed( const CCSWeaponInfo *pWeaponInfo, int nTeamNumber, CEconItemView *pItem )
|
|
{
|
|
CSWeaponID weaponId = WEAPON_NONE;
|
|
CSWeaponType weaponType = WEAPONTYPE_UNKNOWN;
|
|
if ( pItem && pItem->IsValid() )
|
|
{
|
|
weaponId = WeaponIdFromString( pItem->GetStaticData()->GetItemClass() );
|
|
if ( pWeaponInfo )
|
|
weaponType = pWeaponInfo->GetWeaponType( pItem );
|
|
}
|
|
else if ( pWeaponInfo )
|
|
{
|
|
weaponId = pWeaponInfo->m_weaponId;
|
|
weaponType = pWeaponInfo->GetWeaponType( pItem );
|
|
}
|
|
|
|
// prohibited items. currently only supports schema items
|
|
//
|
|
//
|
|
|
|
if ( ( CSGameRules()->m_arrProhibitedItemIndices[ 0 ] != 0 ) && pItem && pItem->IsValid() )
|
|
{
|
|
|
|
int nPosition = pItem->GetItemDefinition()->GetLoadoutSlot( nTeamNumber );
|
|
const CEconItemView* pBaseItem = CSInventoryManager()->GetBaseItemForTeam( nTeamNumber, nPosition );
|
|
|
|
if ( pBaseItem && pBaseItem->IsValid() )
|
|
{
|
|
for ( int j = 0; j < MAX_PROHIBITED_ITEMS; j++ )
|
|
{
|
|
// if the base item is prohibited then the slot is prohibited
|
|
if ( CSGameRules()->m_arrProhibitedItemIndices[ j ] == pBaseItem->GetItemDefinition()->GetDefinitionIndex() )
|
|
{
|
|
return AcquireResult::NotAllowedByProhibition;
|
|
}
|
|
|
|
// if the base item is not prohibited then the alternate item might still be prohibited
|
|
if ( CSGameRules()->m_arrProhibitedItemIndices[ j ] == pItem->GetItemDefinition()->GetDefinitionIndex() )
|
|
{
|
|
return AcquireResult::NotAllowedByProhibition;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
///////////////////////////////
|
|
|
|
switch ( weaponId )
|
|
{
|
|
case ITEM_DEFUSER:
|
|
case ITEM_CUTTERS:
|
|
if ( nTeamNumber == TEAM_TERRORIST )
|
|
{
|
|
return AcquireResult::NotAllowedByTeam;
|
|
}
|
|
if ( !IsBombDefuseMap() )
|
|
{
|
|
return AcquireResult::NotAllowedByMap;
|
|
}
|
|
break;
|
|
|
|
case WEAPON_TASER:
|
|
// special case for limiting taser to classic casual; data drive this if it becomes more complex
|
|
if ( !mp_weapons_allow_zeus.GetBool() )
|
|
{
|
|
return AcquireResult::NotAllowedForPurchase;
|
|
}
|
|
break;
|
|
|
|
case WEAPON_C4:
|
|
if ( nTeamNumber != TEAM_TERRORIST && !IsPlayingTraining() && mp_anyone_can_pickup_c4.GetBool() == false )
|
|
{
|
|
return AcquireResult::NotAllowedByTeam;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return AcquireResult::Allowed;
|
|
}
|
|
|
|
bool CCSGameRules::IsBombDefuseMap() const
|
|
{
|
|
return m_bMapHasBombTarget;
|
|
}
|
|
|
|
bool CCSGameRules::IsHostageRescueMap() const
|
|
{
|
|
return m_bMapHasRescueZone;
|
|
}
|
|
|
|
bool CCSGameRules::MapHasBuyZone() const
|
|
{
|
|
return m_bMapHasBuyZone;
|
|
}
|
|
|
|
bool CCSGameRules::CanSpendMoneyInMap()
|
|
{
|
|
if ( GetMaxMoney() <= 0 )
|
|
return false;
|
|
|
|
if ( PlayerCashAwardsEnabled() == false && TeamCashAwardsEnabled() == false )
|
|
return false;
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
return false;
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
{
|
|
int nTeam = CSGameRules()->IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
|
|
ConVarRef sv_buy_status_override( "sv_buy_status_override" );
|
|
int iBuyStatus = sv_buy_status_override.GetInt();
|
|
if ( iBuyStatus > 0 && (( nTeam == TEAM_CT && iBuyStatus != 1 ) || ( nTeam == TEAM_TERRORIST && iBuyStatus != 2 )) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCSGameRules::IsLogoMap() const
|
|
{
|
|
return m_bLogoMap;
|
|
}
|
|
|
|
float CCSGameRules::GetBuyTimeLength()
|
|
{
|
|
if ( IsWarmupPeriod() )
|
|
{
|
|
if ( IsWarmupPeriodPaused( ) )
|
|
return GetWarmupPeriodEndTime( )-m_fWarmupPeriodStart;
|
|
|
|
if ( mp_buytime.GetFloat() < GetWarmupPeriodEndTime() )
|
|
return GetWarmupPeriodEndTime();
|
|
}
|
|
|
|
return mp_buytime.GetFloat();
|
|
}
|
|
|
|
bool CCSGameRules::IsBuyTimeElapsed()
|
|
{
|
|
if ( IsWarmupPeriod() && IsWarmupPeriodPaused() )
|
|
return false;
|
|
|
|
return ( GetRoundElapsedTime() > GetBuyTimeLength() );
|
|
}
|
|
|
|
bool CCSGameRules::IsMatchWaitingForResume()
|
|
{
|
|
if ( m_bMatchWaitingForResume )
|
|
return true;
|
|
|
|
if ( sv_matchpause_auto_5v5.GetBool() && !IsWarmupPeriod() )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
C_Team
|
|
#else
|
|
CTeam
|
|
#endif
|
|
*arrTeams[2] = { GetGlobalTeam( TEAM_CT ), GetGlobalTeam( TEAM_TERRORIST ) };
|
|
for ( int iTeam = 0; iTeam < Q_ARRAYSIZE( arrTeams ); ++ iTeam )
|
|
{
|
|
if ( !arrTeams[iTeam] ) continue;
|
|
int numPlayers = arrTeams[iTeam]->GetNumPlayers();
|
|
if ( numPlayers < 5 )
|
|
{
|
|
SetMatchWaitingForResume( true ) ;
|
|
return true;
|
|
}
|
|
int numHumans = 0;
|
|
for ( int iPlayer = 0; iPlayer < numPlayers; ++ iPlayer )
|
|
{
|
|
CBasePlayer *pPlayer = arrTeams[iTeam]->GetPlayer( iPlayer );
|
|
if ( !pPlayer ) continue;
|
|
#ifndef CLIENT_DLL
|
|
if ( !pPlayer->IsConnected() ) continue;
|
|
#endif
|
|
if ( pPlayer->IsBot() ) continue;
|
|
++ numHumans;
|
|
}
|
|
if ( numHumans < 5 )
|
|
{
|
|
SetMatchWaitingForResume( true );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int CCSGameRules::DefaultFOV()
|
|
{
|
|
return 90;
|
|
}
|
|
|
|
const CViewVectors* CCSGameRules::GetViewVectors() const
|
|
{
|
|
return &g_CSViewVectors;
|
|
}
|
|
|
|
|
|
#if defined( GAME_DLL)
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CCSGameRules::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker )
|
|
{
|
|
CCSPlayer *pCSAttacker = ToCSPlayer( pAttacker );
|
|
if ( pCSAttacker && PlayerRelationship( pPlayer, pCSAttacker ) == GR_TEAMMATE && !pCSAttacker->IsOtherEnemy( pPlayer->entindex() ) )
|
|
{
|
|
// my teammate hit me.
|
|
if ( ( mp_friendlyfire.GetInt() == 0 ) && ( pCSAttacker != pPlayer ) )
|
|
{
|
|
// friendly fire is off, and this hit came from someone other than myself, then don't get hurt
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return BaseClass::FPlayerCanTakeDamage( pPlayer, pCSAttacker );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CCSGameRules::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled )
|
|
{
|
|
CCSPlayer *pCSAttacker = ToCSPlayer( pAttacker );
|
|
|
|
if ( !pKilled )
|
|
return 0;
|
|
|
|
if ( !pCSAttacker )
|
|
return 1;
|
|
|
|
if ( pCSAttacker != pKilled && PlayerRelationship( pCSAttacker, pKilled ) == GR_TEAMMATE && !pCSAttacker->IsOtherEnemy( pKilled->entindex() ) )
|
|
return -1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
Helper function which handles both voice and chat. The only difference is which convar to use
|
|
to determine whether enemies can be heard (sv_alltalk or sv_allchat).
|
|
*/
|
|
bool CanPlayerHear( CBasePlayer* pListener, CBasePlayer *pSpeaker, bool bTeamOnly, bool bHearEnemies )
|
|
{
|
|
Assert(pListener != NULL && pSpeaker != NULL);
|
|
if ( pListener == NULL || pSpeaker == NULL )
|
|
return false;
|
|
|
|
// sv_full_alltalk lets everyone can talk to everyone else, except comms specifically flagged as team-only
|
|
if ( !bTeamOnly && sv_full_alltalk.GetBool() )
|
|
return true;
|
|
|
|
// if either speaker or listener are coaching then for intents and purposes treat them as teammates.
|
|
int iListenerTeam = pListener->GetAssociatedTeamNumber();
|
|
int iSpeakerTeam = pSpeaker->GetAssociatedTeamNumber();
|
|
|
|
// use the observed target's team when sv_spec_hear is mode 2
|
|
if ( iListenerTeam == TEAM_SPECTATOR && sv_spec_hear.GetInt() == SpecHear::SpectatedTeam &&
|
|
( pListener->GetObserverMode() == OBS_MODE_IN_EYE || pListener->GetObserverMode() == OBS_MODE_CHASE ) )
|
|
{
|
|
CBaseEntity *pTarget = pListener->GetObserverTarget();
|
|
if ( pTarget && pTarget->IsPlayer() )
|
|
{
|
|
iListenerTeam = pTarget->GetTeamNumber();
|
|
}
|
|
}
|
|
|
|
if ( iListenerTeam == TEAM_SPECTATOR )
|
|
{
|
|
if ( sv_spec_hear.GetInt() == SpecHear::Nobody )
|
|
return false; // spectators are selected to not hear other spectators
|
|
|
|
if ( sv_spec_hear.GetInt() == SpecHear::Self )
|
|
return ( pListener == pSpeaker ); // spectators are selected to not hear other spectators
|
|
|
|
// spectators can always hear other spectators
|
|
if ( iSpeakerTeam == TEAM_SPECTATOR )
|
|
return true;
|
|
|
|
return !bTeamOnly && sv_spec_hear.GetInt() == SpecHear::AllPlayers;
|
|
}
|
|
|
|
// no one else can hear spectators
|
|
if ( ( iSpeakerTeam != TEAM_TERRORIST ) &&
|
|
( iSpeakerTeam != TEAM_CT ) )
|
|
return false;
|
|
|
|
// are enemy teams prevented from hearing each other by sv_alltalk/sv_allchat?
|
|
if ( (bTeamOnly || !bHearEnemies) && iSpeakerTeam != iListenerTeam )
|
|
return false;
|
|
|
|
// living players can only hear dead players if sv_deadtalk is enabled
|
|
if ( pListener->IsAlive() && !pSpeaker->IsAlive() )
|
|
{
|
|
return sv_deadtalk.GetBool();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCSGameRules::CanPlayerHearTalker( CBasePlayer* pListener, CBasePlayer *pSpeaker, bool bTeamOnly )
|
|
{
|
|
bool bHearEnemy = false;
|
|
|
|
if ( sv_talk_enemy_living.GetBool() && sv_talk_enemy_dead.GetBool() )
|
|
{
|
|
bHearEnemy = true;
|
|
}
|
|
else if ( !pListener->IsAlive() && !pSpeaker->IsAlive() )
|
|
{
|
|
bHearEnemy = sv_talk_enemy_dead.GetBool();
|
|
}
|
|
else if ( pListener->IsAlive() && pSpeaker->IsAlive() )
|
|
{
|
|
bHearEnemy = sv_talk_enemy_living.GetBool();
|
|
}
|
|
|
|
return CanPlayerHear( pListener, pSpeaker, bTeamOnly, bHearEnemy );
|
|
}
|
|
|
|
extern ConVar sv_allchat;
|
|
bool CCSGameRules::PlayerCanHearChat( CBasePlayer *pListener, CBasePlayer *pSpeaker, bool bTeamOnly )
|
|
{
|
|
return CanPlayerHear( pListener, pSpeaker, bTeamOnly, sv_allchat.GetBool() );
|
|
}
|
|
|
|
int CCSGameRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget )
|
|
{
|
|
|
|
// half life multiplay has a simple concept of Player Relationships.
|
|
// you are either on another player's team, or you are not.
|
|
if ( !pPlayer || !pTarget || !pTarget->IsPlayer() )
|
|
return GR_NOTTEAMMATE;
|
|
|
|
// don't do string compares, just compare the team number
|
|
// if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) )
|
|
// {
|
|
// return GR_TEAMMATE;
|
|
// }
|
|
|
|
CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
|
|
CCSPlayer *pCSTarget = ToCSPlayer( pTarget );
|
|
|
|
if ( pCSPlayer->GetAssociatedTeamNumber() != TEAM_INVALID && pCSTarget->GetAssociatedTeamNumber() != TEAM_INVALID && pCSPlayer->GetAssociatedTeamNumber() == pCSTarget->GetAssociatedTeamNumber() )
|
|
{
|
|
return GR_TEAMMATE;
|
|
}
|
|
|
|
return GR_NOTTEAMMATE;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CCSGameRules::SetTeamRespawnWaveTime( int iTeam, float flValue )
|
|
{
|
|
if ( flValue < 0 )
|
|
{
|
|
flValue = 0;
|
|
}
|
|
|
|
// initialized to -1 so we can try to determine if this is the first spawn time we have received for this team
|
|
// if ( m_flOriginalTeamRespawnWaveTime[iTeam] < 0 )
|
|
// {
|
|
// m_flOriginalTeamRespawnWaveTime[iTeam] = flValue;
|
|
// }
|
|
|
|
m_TeamRespawnWaveTimes.Set( iTeam, flValue );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CCSGameRules::CheckRespawnWaves( void )
|
|
{
|
|
for ( int team = LAST_SHARED_TEAM+1; team < GetNumberOfTeams(); team++ )
|
|
{
|
|
if ( m_flNextRespawnWave[team] > gpGlobals->curtime )
|
|
continue;
|
|
|
|
if ( mp_use_respawn_waves.GetInt() == 2 )
|
|
{
|
|
if ( IsWarmupPeriod() == false && (IsPlayingCoopGuardian() || IsPlayingCoopMission()) )
|
|
{
|
|
// we sometimes don't want to increment the wav number
|
|
if ( m_bDontIncrementCoopWave == false )
|
|
m_nGuardianModeWaveNumber++;
|
|
|
|
m_bDontIncrementCoopWave = false;
|
|
|
|
if ( IsPlayingCoopGuardian() )
|
|
{
|
|
int nTeam = IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->GetTeamNumber() == nTeam )
|
|
{
|
|
//CBroadcastRecipientFilter filter;
|
|
|
|
CRecipientFilter filter;
|
|
filter.AddRecipient( pPlayer );
|
|
pPlayer->EmitSound( filter, pPlayer->entindex(), "UI.DeathMatchBonusAlertEnd" );
|
|
|
|
char szWave[32];
|
|
Q_snprintf( szWave, sizeof( szWave ), "%d", ( int )m_nGuardianModeWaveNumber );
|
|
|
|
const char *szTeam = (nTeam == TEAM_CT) ? "CT" : "T";
|
|
int nMsg = RandomInt( 1, 8 );
|
|
char szNotice[512];
|
|
Q_snprintf( szNotice, sizeof( szNotice ), "#SFUI_Notice_NewWaveBegun_%s%d", szTeam, ( int )nMsg );
|
|
|
|
// send the message
|
|
UTIL_ClientPrintFilter(filter, HUD_PRINTCENTER, szNotice, szWave );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this mode is set, we don't want to set the respawn time to anythign other than infinity, pretty much
|
|
m_flNextRespawnWave.Set( team, gpGlobals->curtime + 99999 );
|
|
}
|
|
else
|
|
m_flNextRespawnWave.Set( team, gpGlobals->curtime + GetRespawnWaveMaxLength( team ) );
|
|
|
|
// respawn all players who are able to respawn here
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->PlayerClass() != 0 && pPlayer->GetTeamNumber() > TEAM_SPECTATOR && pPlayer->GetTeamNumber() == team && !pPlayer->IsAlive() )
|
|
{
|
|
bool bMatchRespawnGamePhase = false;
|
|
switch ( m_match.GetPhase() )
|
|
{
|
|
case GAMEPHASE_WARMUP_ROUND:
|
|
case GAMEPHASE_PLAYING_STANDARD:
|
|
case GAMEPHASE_PLAYING_FIRST_HALF:
|
|
case GAMEPHASE_PLAYING_SECOND_HALF:
|
|
bMatchRespawnGamePhase = true;
|
|
break;
|
|
}
|
|
|
|
if ( ( IsPlayingCoopGuardian() || IsPlayingCoopMission() ) && pPlayer->IsBot() && pPlayer->IsAbleToInstantRespawn() )
|
|
{
|
|
GuardianUpdateBotAccountAndWeapons( pPlayer );
|
|
|
|
// set the bot's name
|
|
CCSBot *bot = dynamic_cast< CCSBot * >( pPlayer );
|
|
if ( bot )
|
|
{
|
|
char botName[MAX_PLAYER_NAME_LENGTH];
|
|
UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
|
|
|
|
engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
|
|
}
|
|
m_nGuardianGrenadesToGiveBots = RandomInt( 2, MIN( 5, m_nGuardianModeWaveNumber ) );
|
|
|
|
// respawn
|
|
pPlayer->State_Transition( STATE_GUNGAME_RESPAWN );
|
|
}
|
|
else if ( bMatchRespawnGamePhase && pPlayer->IsAbleToInstantRespawn() && pPlayer->GetObserverMode() > OBS_MODE_FREEZECAM )
|
|
{
|
|
// respawn
|
|
pPlayer->State_Transition( STATE_GUNGAME_RESPAWN );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::GuardianUpdateBotAccountAndWeapons( CCSPlayer *pBot )
|
|
{
|
|
if ( pBot->IsBot() && mp_use_respawn_waves.GetInt() == 2
|
|
&& IsWarmupPeriod() == false && (IsPlayingCoopGuardian() || IsPlayingCoopMission()) )
|
|
{
|
|
pBot->RemoveAllItems( true );
|
|
pBot->ResetAccount();
|
|
pBot->AddAccount( ( mp_guardian_bot_money_per_wave.GetInt()
|
|
* m_nGuardianModeWaveNumber ), false, false );
|
|
}
|
|
}
|
|
|
|
extern const char* Helper_PickBotGrenade();
|
|
void CCSGameRules::GiveGuardianBotGrenades( CCSPlayer *pBot )
|
|
{
|
|
if ( m_nGuardianGrenadesToGiveBots > 0 && pBot->IsBot() && mp_use_respawn_waves.GetInt() == 2
|
|
&& IsWarmupPeriod() == false && (IsPlayingCoopGuardian() || IsPlayingCoopMission()) )
|
|
{
|
|
const char* szGrenade = Helper_PickBotGrenade();
|
|
if ( szGrenade && pBot->GiveNamedItem( CFmtStr( "weapon_%s", szGrenade ).Access() ) != NULL )
|
|
m_nGuardianGrenadesToGiveBots--;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Is the player past the required delays for spawning
|
|
//-----------------------------------------------------------------------------
|
|
bool CCSGameRules::HasPassedMinRespawnTime( CBasePlayer *pPlayer )
|
|
{
|
|
float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer );
|
|
|
|
return ( gpGlobals->curtime > flMinSpawnTime );
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CCSGameRules::GetRespawnTimeScalar( int iTeam )
|
|
{
|
|
if ( mp_use_respawn_waves.GetInt() == 2 )
|
|
return 1;
|
|
|
|
// For long respawn times, scale the time as the number of players drops
|
|
int iOptimalPlayers = 8; // 16 players total, 8 per team
|
|
|
|
int iNumPlayers = GetGlobalTeam(iTeam)->GetNumPlayers();
|
|
|
|
float flScale = RemapValClamped( iNumPlayers, 1, iOptimalPlayers, 0.25, 1.0 );
|
|
return flScale;
|
|
}
|
|
|
|
bool CCSGameRules::IsEndMatchVotingForNextMapEnabled()
|
|
{
|
|
if ( mp_endmatch_votenextmap.GetBool() )
|
|
{
|
|
//int numMaps = 0;
|
|
if ( g_pGameTypes )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
const char* mapGroupName = gpGlobals->mapGroupName.ToCStr();
|
|
#else
|
|
const char* mapGroupName = engine->GetMapGroupName();
|
|
#endif
|
|
const CUtlStringList* mapsInGroup = g_pGameTypes->GetMapGroupMapList( mapGroupName );
|
|
if ( mapsInGroup )
|
|
return (mapsInGroup->Count() > 1);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCSGameRules::IsEndMatchVotingForNextMap()
|
|
{
|
|
if ( !IsEndMatchVotingForNextMapEnabled() || GetGamePhase() != GAMEPHASE_MATCH_ENDED )
|
|
return false;
|
|
|
|
if ( GetCMMItemDropRevealEndTime() > gpGlobals->curtime )
|
|
return false;
|
|
|
|
if ( m_bIsDroppingItems )
|
|
return false;
|
|
|
|
// // TODO: add a check to make sure items aren't dropping
|
|
// if ( IsEndMatchVotingForNextMapEnabled() && ( GetGamePhase() == GAMEPHASE_MATCH_ENDED ) &&
|
|
// ((gpGlobals->curtime+m_timeUntilNextPhaseStarts) >= gpGlobals->curtime+mp_win_panel_display_time.GetFloat()) && gpGlobals->curtime+GetCMMItemDropRevealEndTime() < gpGlobals->curtime )
|
|
// {
|
|
// return true;
|
|
// }
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: How long are the respawn waves for this team currently?
|
|
//-----------------------------------------------------------------------------
|
|
float CCSGameRules::GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers /* = true */ )
|
|
{
|
|
//Let's just turn off respawn times while players are messing around waiting for the tournament to start
|
|
if ( IsWarmupPeriod() || m_bFreezePeriod )
|
|
return 0.0f;
|
|
|
|
if ( mp_use_respawn_waves.GetInt() == 0 )
|
|
return 0.0f;
|
|
|
|
float flWaveTime = 0;
|
|
if ( iTeam == TEAM_TERRORIST )
|
|
flWaveTime = mp_respawnwavetime_t.GetFloat();
|
|
else if ( iTeam == TEAM_CT )
|
|
flWaveTime = mp_respawnwavetime_ct.GetFloat();
|
|
|
|
//float flTime = ( ( m_TeamRespawnWaveTimes[iTeam] >= 0 ) ? m_TeamRespawnWaveTimes[iTeam] : flWaveTime );
|
|
float flTime = flWaveTime;
|
|
|
|
// For long respawn times, scale the time as the number of players drops
|
|
if ( bScaleWithNumPlayers && flTime > 5 )
|
|
{
|
|
flTime = MAX( 5, flTime * GetRespawnTimeScalar(iTeam) );
|
|
}
|
|
|
|
return flTime;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CCSGameRules::GetMinTimeWhenPlayerMaySpawn( CBasePlayer *pPlayer )
|
|
{
|
|
// Min respawn time is the sum of
|
|
//
|
|
// a) the length of one full *unscaled* respawn wave for their team
|
|
// and
|
|
// b) death anim length + freeze panel length
|
|
|
|
float fMinDelay = 2.0f + GetRespawnWaveMaxLength( pPlayer->GetTeamNumber(), false );
|
|
|
|
return pPlayer->GetDeathTime() + fMinDelay;
|
|
}
|
|
|
|
void CCSGameRules::SetNextTeamRespawnWaveDelay( int iTeam, float flDelay )
|
|
{
|
|
float flNextRespawn = gpGlobals->curtime + flDelay;
|
|
m_flNextRespawnWave.Set( iTeam, flNextRespawn );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: don't let us spawn before our freezepanel time would have ended, even if we skip it
|
|
//-----------------------------------------------------------------------------
|
|
float CCSGameRules::GetNextRespawnWave( int iTeam, CBasePlayer *pPlayer )
|
|
{
|
|
// if ( State_Get() == GR_STATE_STALEMATE )
|
|
// return 0;
|
|
|
|
// If we are purely checking when the next respawn wave is for this team
|
|
if ( pPlayer == NULL )
|
|
{
|
|
return m_flNextRespawnWave[iTeam];
|
|
}
|
|
|
|
// The soonest this player may spawn
|
|
float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer );
|
|
|
|
// the next scheduled respawn wave time
|
|
float flNextRespawnTime = m_flNextRespawnWave[iTeam];
|
|
|
|
// the length of one respawn wave. We'll check in increments of this
|
|
float flRespawnWaveMaxLen = GetRespawnWaveMaxLength( iTeam );
|
|
|
|
if ( flRespawnWaveMaxLen <= 0 )
|
|
{
|
|
return flNextRespawnTime;
|
|
}
|
|
|
|
// Keep adding the length of one respawn until we find a wave that
|
|
// this player will be eligible to spawn in.
|
|
while ( flNextRespawnTime < flMinSpawnTime )
|
|
{
|
|
flNextRespawnTime += flRespawnWaveMaxLen;
|
|
}
|
|
|
|
return flNextRespawnTime;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// 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))
|
|
|
|
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", "ammo_50AE_impulse", 0, 10, 14 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_762MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_762mm_max", "ammo_762mm_impulse", 0, 10, 14 );//Sniper ammo has little force since it just goes through the target.
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_556MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_max", "ammo_556mm_impulse", 0, 10, 14 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_556MM_SMALL, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_small_max","ammo_556mm_impulse", 0, 10, 14 ); // 15 round clip
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_556MM_BOX, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_box_max","ammo_556mm_box_impulse", 0, 10, 14 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_338MAG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_338mag_max", "ammo_338mag_impulse", 0, 12, 16 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_9MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_9mm_max", "ammo_9mm_impulse", 0, 5, 10 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_BUCKSHOT, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_buckshot_max", "ammo_buckshot_impulse", 0, 3, 6 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_45ACP, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_45acp_max", "ammo_45acp_impulse", 0, 6, 10 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_357SIG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_max", "ammo_357sig_impulse", 0, 4, 8 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_357SIG_SMALL,DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_small_max", "ammo_357sig_impulse", 0, 4, 8 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_357SIG_MIN, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_min_max", "ammo_357sig_impulse", 0, 4, 8 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_57MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_57mm_max", "ammo_57mm_impulse", 0, 4, 8 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_HEGRENADE, DMG_BLAST, TRACER_LINE, 0, 0, "ammo_grenade_limit_default", 0, 0, 0 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_FLASHBANG, 0, TRACER_LINE, 0, 0, "ammo_grenade_limit_flashbang", 0, 0, 0 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_SMOKEGRENADE, 0, TRACER_LINE, 0, 0, "ammo_grenade_limit_default", 0, 0, 0 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_MOLOTOV, DMG_BURN, TRACER_NONE, 0, 0, "ammo_grenade_limit_default", 0, 0, 0 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_DECOY, 0, TRACER_NONE, 0, 0, "ammo_grenade_limit_default", 0, 0, 0 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_TASERCHARGE, DMG_SHOCK, TRACER_BEAM, 0, 0, 0, 0, 0, 0 );
|
|
ammoDef.AddAmmoType( BULLET_PLAYER_357SIG_P250, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_p250_max", "ammo_357sig_impulse", 0, 4, 8 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_HEALTHSHOT, 0, TRACER_LINE, 0, 0, "ammo_item_limit_healthshot", 0, 0, 0 );
|
|
ammoDef.AddAmmoType( AMMO_TYPE_TAGRENADE, 0, TRACER_NONE, 0, 0, "ammo_grenade_limit_default", 0, 0, 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_SMALL, 0, 15 );
|
|
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_357SIG_P250, 0, 13 );
|
|
ammoDef.AddAmmoCost( BULLET_PLAYER_357SIG_SMALL, 0, 13 );
|
|
ammoDef.AddAmmoCost( BULLET_PLAYER_357SIG_MIN, 0, 12 );
|
|
ammoDef.AddAmmoCost( BULLET_PLAYER_57MM, 0, 50 );
|
|
}
|
|
|
|
return &ammoDef;
|
|
}
|
|
|
|
bool CCSGameRules::IsRoundOver() const
|
|
{
|
|
return m_iRoundWinStatus != WINNER_NONE;
|
|
}
|
|
|
|
void CCSGameRules::ClearItemsDroppedDuringMatch( void )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
m_bPlayerItemsHaveBeenDisplayed = false;
|
|
#endif
|
|
m_ItemsPtrDroppedDuringMatch.PurgeAndDeleteElements();
|
|
}
|
|
|
|
void CCSGameRules::RecordPlayerItemDrop( const CEconItemPreviewDataBlock &iteminfo )
|
|
{
|
|
if ( !iteminfo.accountid() )
|
|
return;
|
|
|
|
for ( int i = 0; i < m_ItemsPtrDroppedDuringMatch.Count(); i++ )
|
|
{
|
|
// if we've recorded this item already, don't record it again
|
|
if ( m_ItemsPtrDroppedDuringMatch[i]->itemid() == iteminfo.itemid() &&
|
|
m_ItemsPtrDroppedDuringMatch[i]->accountid() == iteminfo.accountid() )
|
|
return;
|
|
}
|
|
|
|
// on the client, we add all local player items to the top of the list
|
|
// server just adds them
|
|
#if defined( CLIENT_DLL )
|
|
// don't put the local player's at the top anymore - matt wood
|
|
// C_CSPlayer *pLocalPlayer = C_CSPlayer::GetLocalCSPlayer();
|
|
// C_CS_PlayerResource *cs_PR = dynamic_cast<C_CS_PlayerResource *>( g_PR );
|
|
// if ( cs_PR && pLocalPlayer )
|
|
// {
|
|
// XUID localXuid = cs_PR->GetXuid( pLocalPlayer->entindex() );
|
|
// if (localXuid == steamOwnerID.ConvertToUint64())
|
|
// {
|
|
// m_ItemsPtrDroppedDuringMatch.AddToHead( event );
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
if ( CDemoPlaybackParameters_t const *pParams = engine->GetDemoPlaybackParameters() )
|
|
{ // When playing back Overwatch with anonymous player identities don't collect drops on client
|
|
if ( pParams->m_bAnonymousPlayerIdentity )
|
|
return;
|
|
}
|
|
|
|
// check to see if this player is still connected
|
|
bool bFoundPlayer = false;
|
|
for ( int j = 1; j <= MAX_PLAYERS; j++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( j ) );
|
|
if ( pPlayer )
|
|
{
|
|
CSteamID steamID;
|
|
pPlayer->GetSteamID( &steamID );
|
|
if ( steamID.GetAccountID() == iteminfo.accountid() )
|
|
{
|
|
bFoundPlayer = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check to see if this player is still connected
|
|
if ( bFoundPlayer )
|
|
m_ItemsPtrDroppedDuringMatch.AddToTail( new CEconItemPreviewDataBlock( iteminfo ) );
|
|
|
|
#else
|
|
m_ItemsPtrDroppedDuringMatch.AddToTail( new CEconItemPreviewDataBlock( iteminfo ) );
|
|
#endif
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
const char *CCSGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer )
|
|
{
|
|
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->GetAssociatedTeamNumber() == 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->GetAssociatedTeamNumber() == 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->IsSpectator() )
|
|
{
|
|
pszFormat = "Cstrike_Chat_Spec";
|
|
}
|
|
}
|
|
// everyone
|
|
else
|
|
{
|
|
if ( pPlayer->m_lifeState == LIFE_ALIVE )
|
|
{
|
|
pszFormat = "Cstrike_Chat_All";
|
|
}
|
|
else
|
|
{
|
|
if ( !pPlayer->IsSpectator() )
|
|
{
|
|
pszFormat = "Cstrike_Chat_AllDead";
|
|
}
|
|
else
|
|
{
|
|
pszFormat = "Cstrike_Chat_AllSpec";
|
|
}
|
|
}
|
|
}
|
|
|
|
return pszFormat;
|
|
}
|
|
|
|
void CCSGameRules::ClientSettingsChanged( CBasePlayer *pPlayer )
|
|
{
|
|
CCSPlayer *pCSPlayer = dynamic_cast<CCSPlayer*>( pPlayer );
|
|
if ( pCSPlayer )
|
|
{
|
|
// NOTE[pmf]: Before testing if the name has changed, we first copy it into a buffer of MAX_PLAYER_NAME_LENGTH length,
|
|
// trimming malformed utf8 (truncated) characters at the end (which we get from Steam); otherwise, when this
|
|
// correction happens as a result of CBasePlayer::SetPlayerName(), we'll always see the name as different,
|
|
// and continue to spam name change attempts.
|
|
if ( CSGameRules()->IsPlayingCoopMission() && pCSPlayer->IsBot() )
|
|
{
|
|
pCSPlayer->ChangeName( pCSPlayer->GetPlayerName() );
|
|
}
|
|
else if ( CanClientCustomizeOwnIdentity() )
|
|
{
|
|
char szNewName[MAX_PLAYER_NAME_LENGTH];
|
|
V_UTF8_strncpy( szNewName, engine->GetClientConVarValue( pCSPlayer->entindex(), "name" ), sizeof(szNewName) );
|
|
const char *pszOldName = pCSPlayer->GetPlayerName();
|
|
if ( pszOldName[0] != 0 && Q_strcmp( pszOldName, szNewName ) )
|
|
{
|
|
pCSPlayer->ChangeName( szNewName );
|
|
}
|
|
}
|
|
|
|
pCSPlayer->m_bShowHints = true;
|
|
if ( pCSPlayer->IsNetClient() )
|
|
{
|
|
const char *pShowHints = engine->GetClientConVarValue( ENTINDEX( pCSPlayer->edict() ), "cl_autohelp" );
|
|
if ( pShowHints && atoi( pShowHints ) <= 0 )
|
|
{
|
|
pCSPlayer->m_bShowHints = false;
|
|
}
|
|
}
|
|
|
|
//pCSPlayer->UpdateAppearanceIndex();
|
|
|
|
pCSPlayer->InitTeammatePreferredColor();
|
|
}
|
|
}
|
|
|
|
bool CCSGameRules::CanClientCustomizeOwnIdentity()
|
|
{
|
|
return !CCSGameRules::sm_QueuedServerReservation.tournament_teams().size();
|
|
}
|
|
|
|
bool CCSGameRules::FAllowNPCs( void )
|
|
{
|
|
if ( IsPlayingTraining() )
|
|
return true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCSGameRules::IsFriendlyFireOn( void ) const
|
|
{
|
|
return mp_friendlyfire.GetBool();
|
|
}
|
|
|
|
bool CCSGameRules::IsLastRoundBeforeHalfTime( void )
|
|
{
|
|
if ( HasHalfTime() )
|
|
{
|
|
int numRoundsBeforeHalftime = -1;
|
|
if ( m_match.GetPhase() == GAMEPHASE_PLAYING_FIRST_HALF )
|
|
numRoundsBeforeHalftime = GetOvertimePlaying()
|
|
? ( mp_maxrounds.GetInt() + ( 2 * GetOvertimePlaying() - 1 ) * ( mp_overtime_maxrounds.GetInt() / 2 ) )
|
|
: ( mp_maxrounds.GetInt() / 2 );
|
|
|
|
if ( ( numRoundsBeforeHalftime > 0 ) && ( m_match.GetRoundsPlayed() == (numRoundsBeforeHalftime-1) ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CON_COMMAND( map_showspawnpoints, "Shows player spawn points (red=invalid). Optionally pass in the duration." )
|
|
{
|
|
int duration = 600;
|
|
|
|
if ( args.ArgC() == 2 )
|
|
{
|
|
duration = Q_atoi( args[1] );
|
|
}
|
|
|
|
CSGameRules()->ShowSpawnPoints( duration );
|
|
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Test for "pistol" round, defined as the default starting round
|
|
// when players cannot purchase anything primary weapons
|
|
//-----------------------------------------------------------------------------
|
|
bool CCSGameRules::IsPistolRound()
|
|
{
|
|
if ( !IsPlayingClassic() )
|
|
return false;
|
|
|
|
return ( ( m_iTotalRoundsPlayed == 0 ) ||
|
|
( HasHalfTime() && ( m_iTotalRoundsPlayed > 0 ) && ( m_iTotalRoundsPlayed == ( mp_maxrounds.GetInt() / 2 ) ) ) )
|
|
&& ( GetStartMoney() <= 800 );
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
CCSMatch* CCSGameRules::GetMatch( void )
|
|
{
|
|
return &m_match;
|
|
}
|
|
|
|
void CCSGameRules::SendPlayerItemDropsToClient()
|
|
{
|
|
CReliableBroadcastRecipientFilter broadcastFilter;
|
|
CCSUsrMsg_SendPlayerItemDrops msg;
|
|
|
|
// first randomize the list before we send it
|
|
VectorShuffle( m_ItemsPtrDroppedDuringMatch );
|
|
|
|
msg.mutable_entity_updates()->Reserve( m_ItemsPtrDroppedDuringMatch.Count() );
|
|
for ( int i = 0; i < m_ItemsPtrDroppedDuringMatch.Count(); i++ )
|
|
{
|
|
*msg.add_entity_updates() = *m_ItemsPtrDroppedDuringMatch[i];
|
|
}
|
|
|
|
SendUserMessage( broadcastFilter, CS_UM_SendPlayerItemDrops, msg );
|
|
|
|
m_bPlayerItemsHaveBeenDisplayed = true;
|
|
}
|
|
|
|
void CCSGameRules::FreezePlayers( void )
|
|
{
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->AddFlag( FL_FROZEN );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ClearGunGameData( void )
|
|
{
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
// Reset all players' progressive weapon index values
|
|
pPlayer->ResetTRBombModeData();
|
|
pPlayer->ClearGunGameProgressiveWeaponIndex();
|
|
pPlayer->ResetTRBombModeWeaponProgressFlag();
|
|
pPlayer->ResetTRBombModeKillPoints();
|
|
pPlayer->ClearTRModeHEGrenade();
|
|
pPlayer->ClearTRModeFlashbang();
|
|
pPlayer->ClearTRModeMolotov();
|
|
pPlayer->ClearTRModeIncendiary();
|
|
}
|
|
}
|
|
|
|
m_iMaxGunGameProgressiveWeaponIndex = 0;
|
|
}
|
|
|
|
|
|
void CCSGameRules::SwitchTeamsAtRoundReset( void )
|
|
{
|
|
m_bForceTeamChangeSilent = true;
|
|
m_bSwitchingTeamsAtRoundReset = true;
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->SetPendingTeamNum( pPlayer->GetTeamNumber() ) ;
|
|
if ( pPlayer->GetAssociatedTeamNumber() == TEAM_CT || pPlayer->GetAssociatedTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
pPlayer->SwitchTeamsAtRoundReset();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Swap custom teamnames
|
|
}
|
|
|
|
void CCSGameRules::AddTeamAccount( int team, TeamCashAward::Type reason )
|
|
{
|
|
int amount = TeamCashAwardValue( reason );
|
|
|
|
AddTeamAccount( team, reason, amount );
|
|
}
|
|
|
|
void CCSGameRules::AddTeamAccount( int team, TeamCashAward::Type reason, int amount, const char* szAwardText )
|
|
{
|
|
if ( !TeamCashAwardsEnabled() )
|
|
{
|
|
DevMsg( "WARNING: Trying to award %d to team %d, but mp_teamcashawards is 0!\n", amount, team );
|
|
return;
|
|
}
|
|
|
|
if ( amount == 0 )
|
|
return;
|
|
|
|
if ( IsPlayingCoopGuardian() || IsPlayingCoopMission() )
|
|
{
|
|
int nOtherTeam = IsHostageRescueMap() ? TEAM_CT : TEAM_TERRORIST;
|
|
// in this mode, bots are on the other side and we handle their money ourselves
|
|
if ( team == nOtherTeam )
|
|
return;
|
|
}
|
|
|
|
// float flDisplayedBonus = 1.0f;
|
|
//
|
|
// if ( CSGameRules() )
|
|
// {
|
|
// // Modify with slow money multiplier rules
|
|
// amount = CSGameRules()->ModifyMoneyRewardByMultiplier( true, amount, &flDisplayedBonus );
|
|
// }
|
|
|
|
char customAwardText[256];
|
|
bool bUsingCustom = false;
|
|
const char* awardReasonToken = NULL;
|
|
|
|
switch ( reason )
|
|
{
|
|
case TeamCashAward::TERRORIST_WIN_BOMB:
|
|
awardReasonToken = "#Team_Cash_Award_T_Win_Bomb";
|
|
break;
|
|
case TeamCashAward::ELIMINATION_HOSTAGE_MAP_T:
|
|
case TeamCashAward::ELIMINATION_HOSTAGE_MAP_CT:
|
|
awardReasonToken = "#Team_Cash_Award_Elim_Hostage";
|
|
break;
|
|
case TeamCashAward::ELIMINATION_BOMB_MAP:
|
|
awardReasonToken = "#Team_Cash_Award_Elim_Bomb";
|
|
break;
|
|
case TeamCashAward::WIN_BY_TIME_RUNNING_OUT_HOSTAGE:
|
|
case TeamCashAward::WIN_BY_TIME_RUNNING_OUT_BOMB:
|
|
awardReasonToken = "#Team_Cash_Award_Win_Time";
|
|
break;
|
|
case TeamCashAward::WIN_BY_DEFUSING_BOMB:
|
|
awardReasonToken = "#Team_Cash_Award_Win_Defuse_Bomb";
|
|
break;
|
|
case TeamCashAward::WIN_BY_HOSTAGE_RESCUE:
|
|
if ( mp_hostages_rescuetowin.GetInt() == 1 )
|
|
awardReasonToken = "#Team_Cash_Award_Win_Hostage_Rescue";
|
|
else
|
|
awardReasonToken = "#Team_Cash_Award_Win_Hostages_Rescue";
|
|
break;
|
|
case TeamCashAward::LOSER_BONUS:
|
|
if ( amount > 0 )
|
|
awardReasonToken = "#Team_Cash_Award_Loser_Bonus";
|
|
else
|
|
awardReasonToken = "#Team_Cash_Award_Loser_Bonus_Neg";
|
|
|
|
|
|
break;
|
|
case TeamCashAward::RESCUED_HOSTAGE:
|
|
awardReasonToken = "#Team_Cash_Award_Rescued_Hostage";
|
|
break;
|
|
case TeamCashAward::HOSTAGE_INTERACTION:
|
|
awardReasonToken = "#Team_Cash_Award_Hostage_Interaction";
|
|
break;
|
|
case TeamCashAward::HOSTAGE_ALIVE:
|
|
awardReasonToken = "#Team_Cash_Award_Hostage_Alive";
|
|
break;
|
|
case TeamCashAward::PLANTED_BOMB_BUT_DEFUSED:
|
|
awardReasonToken = "#Team_Cash_Award_Planted_Bomb_But_Defused";
|
|
break;
|
|
case TeamCashAward::SURVIVE_GUARDIAN_WAVE:
|
|
awardReasonToken = "#Team_Cash_Award_Survive_GuardianMode_Wave";
|
|
break;
|
|
|
|
case TeamCashAward::CUSTOM_AWARD:
|
|
{
|
|
if ( szAwardText && szAwardText[0] != 0 )
|
|
{
|
|
awardReasonToken = "#Team_Cash_Award_Custom";
|
|
Q_snprintf( customAwardText, sizeof( customAwardText ), "%s", szAwardText );
|
|
bUsingCustom = true;
|
|
}
|
|
else
|
|
{
|
|
awardReasonToken = "#Team_Cash_Award_Generic";
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
bool bTeamHasClinchedVictory = false;
|
|
if ( IsPlayingClassic() && !IsPlayingTraining() )
|
|
{
|
|
int iNumWinsToClinch = ( mp_maxrounds.GetInt() / 2 ) + 1 + GetOvertimePlaying() * ( mp_overtime_maxrounds.GetInt() / 2 );
|
|
bTeamHasClinchedVictory = mp_match_can_clinch.GetBool() && ( m_match.GetCTScore() >= iNumWinsToClinch ) || ( m_match.GetTerroristScore() >= iNumWinsToClinch );
|
|
}
|
|
|
|
char strAmount[64];
|
|
Q_snprintf( strAmount, sizeof( strAmount ), "%s$%d", amount >= 0 ? "+" : "-", abs( amount ));
|
|
|
|
// Update all QMM records for the teams
|
|
bool bTeamsArePlayingSwitchedSides = AreTeamsPlayingSwitchedSides();
|
|
FOR_EACH_MAP_FAST( m_mapQueuedMatchmakingPlayersData, idxQMMEntry )
|
|
{
|
|
CQMMPlayerData_t &qmmData = * m_mapQueuedMatchmakingPlayersData.Element( idxQMMEntry );
|
|
int iRulesTeam = bTeamsArePlayingSwitchedSides ? ( (qmmData.m_iDraftIndex < 5)?TEAM_TERRORIST:TEAM_CT ) : ( (qmmData.m_iDraftIndex < 5)?TEAM_CT:TEAM_TERRORIST );
|
|
if ( ( iRulesTeam == team ) && !qmmData.m_bReceiveNoMoneyNextRound )
|
|
{ // we set all records even though alive players will stomp them when receiving money in the loop right below
|
|
qmmData.m_cash = clamp( (int)( qmmData.m_cash + amount ), 0, GetMaxMoney() );
|
|
}
|
|
}
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer || pPlayer->GetTeamNumber() != team )
|
|
continue;
|
|
|
|
// hand out team cash awards from previous round
|
|
if ( pPlayer->DoesPlayerGetRoundStartMoney() )
|
|
{
|
|
pPlayer->AddAccountFromTeam( amount, true, reason );
|
|
if ( dev_reportmoneychanges.GetBool() )
|
|
Msg( "%s %d (total: %d) AddAccountFromTeam: REASON: %s\n", pPlayer->GetPlayerName(), amount, pPlayer->GetAccountBalance(), awardReasonToken );
|
|
|
|
if ( bUsingCustom )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, awardReasonToken, strAmount, customAwardText );
|
|
}
|
|
else if ( !IsLastRoundBeforeHalfTime() && ( m_match.GetPhase() != GAMEPHASE_HALFTIME ) &&
|
|
( m_match.GetRoundsPlayed() != mp_maxrounds.GetInt() + GetOvertimePlaying() * mp_overtime_maxrounds.GetInt() ) && !bTeamHasClinchedVictory )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, awardReasonToken, strAmount );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !IsLastRoundBeforeHalfTime() && ( m_match.GetPhase() != GAMEPHASE_HALFTIME ) &&
|
|
( m_match.GetRoundsPlayed() != mp_maxrounds.GetInt() + GetOvertimePlaying() * mp_overtime_maxrounds.GetInt() ) && !bTeamHasClinchedVictory )
|
|
{
|
|
// TODO: This code assumes on there only being 2 possible reasons for DoesPlayerGetRoundStartMoney returning false: Suicide or Running down the clock as T.
|
|
// This code should not make that assumption and the awardReasonToken should probably be plumbed to express those properly.
|
|
if ( pPlayer->GetHealth() > 0 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, "#Team_Cash_Award_No_Income", strAmount );
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTTALK, "#Team_Cash_Award_No_Income_Suicide", strAmount );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // !CLIENT_DLL
|
|
|
|
bool CCSGameRules::IsCSGOBirthday( void )
|
|
{
|
|
return sv_party_mode.GetBool();
|
|
}
|
|
|
|
int CCSGameRules::GetStartMoney( void )
|
|
{
|
|
return IsWarmupPeriod() ? GetMaxMoney() : ( GetOvertimePlaying() ? mp_overtime_startmoney.GetInt() : mp_startmoney.GetInt() );
|
|
}
|
|
|
|
|
|
int CCSGameRules::GetMaxMoney( void )
|
|
{
|
|
return mp_maxmoney.GetInt();
|
|
}
|
|
|
|
|
|
int CCSGameRules::GetBetweenRoundMoney( void )
|
|
{
|
|
return mp_afterroundmoney.GetInt();
|
|
}
|
|
|
|
|
|
bool CCSGameRules::PlayerCashAwardsEnabled( void )
|
|
{
|
|
return mp_playercashawards.GetBool();
|
|
}
|
|
|
|
|
|
bool CCSGameRules::TeamCashAwardsEnabled( void )
|
|
{
|
|
return mp_teamcashawards.GetBool();
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingGunGameProgressive( void ) const
|
|
{
|
|
return ( IsPlayingGunGame() &&
|
|
g_pGameTypes->GetCurrentGameMode() == CS_GameMode::GunGame_Progressive );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingGunGameDeathmatch( void ) const
|
|
{
|
|
return ( IsPlayingGunGame() &&
|
|
g_pGameTypes->GetCurrentGameMode() == CS_GameMode::GunGame_Deathmatch );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingGunGameTRBomb( void ) const
|
|
{
|
|
return ( IsPlayingGunGame() &&
|
|
g_pGameTypes->GetCurrentGameMode() == CS_GameMode::GunGame_Bomb );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingClassicCasual( void ) const
|
|
{
|
|
return ( IsPlayingClassic() &&
|
|
g_pGameTypes->GetCurrentGameMode() == CS_GameMode::Classic_Casual);
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingAnyCompetitiveStrictRuleset( void ) const
|
|
{
|
|
return ( IsPlayingClassic() && ( (g_pGameTypes->GetCurrentGameMode() == CS_GameMode::Classic_Competitive) ) );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingCoopGuardian( void ) const
|
|
{
|
|
return ( IsPlayingCooperativeGametype() &&
|
|
g_pGameTypes->GetCurrentGameMode() == CS_GameMode::Cooperative_Guardian );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingCoopMission( void ) const
|
|
{
|
|
return ( IsPlayingCooperativeGametype() &&
|
|
g_pGameTypes->GetCurrentGameMode() == CS_GameMode::Cooperative_Mission );
|
|
}
|
|
|
|
bool CCSGameRules::ShouldRecordMatchStats( void ) const
|
|
{
|
|
return ( IsPlayingCoopMission() || (IsPlayingClassic() &&
|
|
( g_pGameTypes->GetCurrentGameMode() == CS_GameMode::Classic_Competitive )) &&
|
|
( !IsWarmupPeriod() ) &&
|
|
( m_nOvertimePlaying == 0 ) &&
|
|
( GetTotalRoundsPlayed() < MAX_MATCH_STATS_ROUNDS ) );
|
|
}
|
|
|
|
bool CCSGameRules::IsQueuedMatchmaking() const
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
static ConVarRef sv_mmqueue_reservation( "sv_mmqueue_reservation" );
|
|
return sv_mmqueue_reservation.GetString()[0] == 'Q';
|
|
#else
|
|
return m_bIsQueuedMatchmaking.Get();
|
|
#endif
|
|
}
|
|
|
|
bool CCSGameRules::IsValveDS() const
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
return false;
|
|
#else
|
|
return m_bIsValveDS.Get();
|
|
#endif
|
|
}
|
|
|
|
bool CCSGameRules::IsQuestEligible() const
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
return false;
|
|
#else
|
|
return m_bIsQuestEligible.Get();
|
|
#endif
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingOffline( void ) const
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
if ( engine->IsDedicatedServer() )
|
|
return false;
|
|
#endif
|
|
extern ConVar game_online;
|
|
return !game_online.GetBool();
|
|
}
|
|
|
|
bool CCSGameRules::IsAwardsProgressAllowedForBotDifficulty( void ) const
|
|
{
|
|
return ( !IsPlayingOffline() || ( GetCustomBotDifficulty() >= CUSTOM_BOT_MIN_DIFFICULTY_FOR_AWARDS_PROGRESS ) );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingCustomGametype( void ) const
|
|
{
|
|
return ( g_pGameTypes->GetCurrentGameType() == CS_GameType_Custom );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingTraining( void ) const
|
|
{
|
|
return ( g_pGameTypes->GetCurrentGameType() == CS_GameType_Training );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingClassic( void ) const
|
|
{
|
|
return ( g_pGameTypes->GetCurrentGameType() == CS_GameType_Classic );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingGunGame( void ) const
|
|
{
|
|
return ( g_pGameTypes->GetCurrentGameType() == CS_GameType_GunGame );
|
|
}
|
|
|
|
bool CCSGameRules::IsPlayingCooperativeGametype( void ) const
|
|
{
|
|
return ( g_pGameTypes->GetCurrentGameType() == CS_GameType_Cooperative );
|
|
}
|
|
|
|
int CCSGameRules::GetCustomBotDifficulty( void ) const
|
|
{
|
|
return ( g_pGameTypes->GetCustomBotDifficulty() );
|
|
}
|
|
|
|
bool CCSGameRules::ForceSplitScreenPlayersOnToSameTeam( void )
|
|
{
|
|
extern ConVar game_online;
|
|
return game_online.GetBool();
|
|
}
|
|
|
|
ConVar mp_solid_teammates("mp_solid_teammates", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines whether teammates are solid or not." );
|
|
ConVar mp_free_armor("mp_free_armor", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines whether armor and helmet are given automatically." );
|
|
ConVar mp_halftime("mp_halftime", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines whether the match switches sides in a halftime event.");
|
|
ConVar mp_randomspawn("mp_randomspawn", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines whether players are to spawn. 0 = default; 1 = both teams; 2 = Terrorists; 3 = CTs." );
|
|
ConVar mp_randomspawn_los("mp_randomspawn_los", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If using mp_randomspawn, determines whether to test Line of Sight when spawning." );
|
|
ConVar mp_randomspawn_dist( "mp_randomspawn_dist", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "If using mp_randomspawn, determines whether to test distance when selecting this spot." );
|
|
|
|
// Returns true if teammates are solid obstacles in the current game mode
|
|
bool CCSGameRules::IsTeammateSolid( void ) const
|
|
{
|
|
return mp_solid_teammates.GetBool();
|
|
}
|
|
|
|
// Returns true if armor is given automatically.
|
|
bool CCSGameRules::IsArmorFree( void ) const
|
|
{
|
|
return mp_free_armor.GetBool();
|
|
}
|
|
|
|
// Returns true if the game is to be split into two halves.
|
|
bool CCSGameRules::HasHalfTime( void ) const
|
|
{
|
|
return mp_halftime.GetBool();
|
|
}
|
|
|
|
int CCSGameRules::GetWeaponScoreForDeathmatch( int nPos )
|
|
{
|
|
int nScore = 1;
|
|
|
|
CSWeaponID wepID = WEAPON_NONE;
|
|
|
|
for ( int i = 0; i < GetItemSchema()->GetItemDefinitionCount(); i++ )
|
|
{
|
|
CCStrike15ItemDefinition *pItemDef = ( CCStrike15ItemDefinition * )GetItemSchema()->GetItemDefinitionByMapIndex( i );
|
|
|
|
if ( pItemDef->GetDefaultLoadoutSlot() == nPos )
|
|
{
|
|
wepID = WeaponIdFromString( pItemDef->GetItemClass() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( wepID == WEAPON_NONE )
|
|
return 0;
|
|
|
|
const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo( wepID );
|
|
if ( pWeaponInfo )
|
|
{
|
|
// float flScore1 = ((pWeaponInfo->m_flCycleTime / pWeaponInfo->m_iDamage)-0.001f) * 10;
|
|
// float flScore2 = ((pWeaponInfo->m_iKillAward / MAX( 500, pWeaponInfo->m_iWeaponPrice )) + flScore1) * 100;
|
|
// int nScore = MAX( 1, ceil(flScore2) );
|
|
// pPlayer->AddContributionScore( nScore );
|
|
|
|
if ( wepID == WEAPON_KNIFE )
|
|
{
|
|
nScore = 20;
|
|
}
|
|
else
|
|
{
|
|
int nPrice = MIN( 4500, MAX(100, pWeaponInfo->GetWeaponPrice() - (pWeaponInfo->GetKillAward()/2)) );
|
|
float flScore1 = MAX( 0.05f, ((1 - ((float)nPrice/4500.0f)) * 10)/5);
|
|
float flScore2 = flScore1 + MIN( 2.0f, (((pWeaponInfo->GetCycleTime() / pWeaponInfo->GetDamage() ) *10 ) + ( ( 2.0f-pWeaponInfo->GetArmorRatio() ) /2 ) ) / 4 );
|
|
float flFinal = ceil(flScore2-0.5f);//ceil((flScore2/6) * 10 );
|
|
nScore = MAX( 0, flFinal ) + 10;
|
|
}
|
|
}
|
|
|
|
return nScore;
|
|
}
|
|
|
|
|
|
|
|
float CCSGameRules::GetRestartRoundTime( void ) const
|
|
{
|
|
return m_fRoundStartTime;
|
|
}
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
CGameCoopMissionManager *CCSGameRules::GetCoopMissionManager( void )
|
|
{
|
|
CGameCoopMissionManager *pManager = static_cast< CGameCoopMissionManager* >( m_coopMissionManager.Get() );
|
|
if ( !pManager )
|
|
{
|
|
CBaseEntity *ent = NULL;
|
|
do
|
|
{
|
|
ent = gEntList.FindEntityByClassname( ent, "game_coopmission_manager" );
|
|
|
|
if ( ent )
|
|
{
|
|
CSGameRules()->SetCoopMissionManager( ent );
|
|
pManager = GetCoopMissionManager();
|
|
}
|
|
|
|
} while ( ent );
|
|
}
|
|
|
|
return pManager;
|
|
}
|
|
|
|
void CCSGameRules::CoopSetBotQuotaAndRefreshSpawns( int nMaxEnemiesToSpawn )
|
|
{
|
|
RefreshCurrentSpawnPointLists();
|
|
|
|
m_iMaxNumTerrorists = nMaxEnemiesToSpawn;
|
|
|
|
ConVarRef bot_quota( "bot_quota" );
|
|
int nQuota = UTIL_HumansInGame( true, true ) + MIN( m_iMaxNumTerrorists, m_iSpawnPointCount_Terrorist );
|
|
bot_quota.SetValue( nQuota );
|
|
|
|
ShuffleSpawnPointLists();
|
|
SortSpawnPointLists();
|
|
}
|
|
|
|
void CCSGameRules::CoopMissionSpawnFirstEnemies( int nMaxEnemiesToSpawn )
|
|
{
|
|
// set to 0 so we start in wave 1
|
|
m_nGuardianModeWaveNumber = 0;
|
|
|
|
CoopMissionSpawnNextWave( nMaxEnemiesToSpawn );
|
|
}
|
|
|
|
void CCSGameRules::CoopMissionSetNextRespawnIn( float flSeconds, bool bIncrementWaveNumber )
|
|
{
|
|
m_bDontIncrementCoopWave = !bIncrementWaveNumber;
|
|
|
|
// if T's are supposed to respawn
|
|
float flNextRespawn = gpGlobals->curtime + flSeconds;
|
|
m_flNextRespawnWave.Set( TEAM_TERRORIST, flNextRespawn );
|
|
}
|
|
|
|
void CCSGameRules::CoopMissionSpawnNextWave( int nMaxEnemiesToSpawn )
|
|
{
|
|
// the CT spawn points for respawn are enabled, respawn now - we can work on the appropriate timings later
|
|
int nTeam = TEAM_CT;
|
|
// give health to all of the players on the other team
|
|
for ( int j = 1; j <= MAX_PLAYERS; j++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( j ) );
|
|
if ( pPlayer && pPlayer->GetTeamNumber() == nTeam )
|
|
{
|
|
// respawn any dead CTs as well
|
|
if ( pPlayer->IsAlive() == false )
|
|
{
|
|
pPlayer->State_Transition( STATE_GUNGAME_RESPAWN );
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure this is 0 so we start from the beginning
|
|
m_iNextTerroristSpawnPoint = 0;
|
|
|
|
// do a refresh here, but we don't refresh as frequently in coop mission
|
|
// we would normally refresh every time a spot enabled or disabled and a few other places
|
|
// just do it here once before we spawn new guys
|
|
RefreshCurrentSpawnPointLists();
|
|
|
|
m_iMaxNumTerrorists = nMaxEnemiesToSpawn;
|
|
|
|
ShuffleSpawnPointLists();
|
|
SortSpawnPointLists();
|
|
|
|
// make sure that there are always enough bots
|
|
ConVarRef bot_quota( "bot_quota" );
|
|
int nQuota = UTIL_HumansInGame( true, true ) + MIN( m_iMaxNumTerrorists, m_iSpawnPointCount_Terrorist );
|
|
bot_quota.SetValue( nQuota );
|
|
|
|
//DevMsg( ">> Setting bot_quota to %d\n", nQuota );
|
|
|
|
// if T's are supposed to respawn
|
|
CoopMissionSetNextRespawnIn( -1, true );
|
|
}
|
|
|
|
void CCSGameRules::CoopMissionRespawnDeadPlayers( void )
|
|
{
|
|
// do a refresh here, but we don't refresh as frequently in coop mission
|
|
// we would normally refresh every time a spot enabled or disabled and a few other places
|
|
// just do it here once before we spawn new guys
|
|
|
|
if ( !IsWarmupPeriod() )
|
|
RefreshCurrentSpawnPointLists();
|
|
|
|
// the CT spawn points for respawn are enabled, respawn now - we can work on the appropriate timings later
|
|
CTeam *pTeam = GetGlobalTeam( TEAM_CT );
|
|
CUtlVector < CCSPlayer* > vecPlayers;
|
|
if ( pTeam->GetHumanMembers( &vecPlayers ) )
|
|
{
|
|
FOR_EACH_VEC( vecPlayers, i )
|
|
{
|
|
// respawn any dead CTs as well
|
|
if ( vecPlayers[i]->IsAlive() == false )
|
|
{
|
|
vecPlayers[i]->State_Transition( STATE_GUNGAME_RESPAWN );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::CoopCollectBonusCoin( void )
|
|
{
|
|
m_coopBonusCoinsFound++;
|
|
CRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
filter.AddRecipientsByTeam( GetGlobalTeam( TEAM_CT ) );
|
|
char szCoins[32];
|
|
Q_snprintf( szCoins, sizeof( szCoins ), "%d", ( int )m_coopBonusCoinsFound );
|
|
UTIL_ClientPrintFilter( filter, HUD_PRINTCENTER, "#SFUIHUD_InfoPanel_Coop_CollectCoin", szCoins );
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
CCSGameRules::CCSGameRules()
|
|
{
|
|
// Reset borrow music convar because player indices scramble after level transition.
|
|
cl_borrow_music_from_player_index.SetValue( cl_borrow_music_from_player_index.GetDefault() );
|
|
|
|
InitializeGameTypeAndMode();
|
|
|
|
// Set the bestof maps
|
|
m_numBestOfMaps = 0;
|
|
|
|
// Set global gifts state
|
|
m_numGlobalGiftsGiven = 0;
|
|
m_numGlobalGifters = 0;
|
|
m_numGlobalGiftsPeriodSeconds = 0;
|
|
for ( int j = 0; j < MAX_GIFT_GIVERS_FEATURED_COUNT; ++ j )
|
|
{
|
|
m_arrFeaturedGiftersAccounts.Set( j, 0 );
|
|
m_arrFeaturedGiftersGifts.Set( j, 0 );
|
|
}
|
|
|
|
for ( int j = 0; j < MAX_TOURNAMENT_ACTIVE_CASTER_COUNT; ++ j )
|
|
{
|
|
m_arrTournamentActiveCasterAccounts.Set( j, 0 );
|
|
}
|
|
|
|
for ( int j = 0; j < MAX_MATCH_STATS_ROUNDS; ++j )
|
|
{
|
|
m_iMatchStats_PlayersAlive_T.GetForModify( j ) = 0x3F;
|
|
m_iMatchStats_PlayersAlive_CT.GetForModify( j ) = 0x3F;
|
|
}
|
|
|
|
ResetCasterConvars();
|
|
|
|
m_szTournamentEventName.GetForModify()[0] = 0;
|
|
m_szTournamentEventStage.GetForModify()[0] = 0;
|
|
m_szTournamentPredictionsTxt.GetForModify()[0] = 0;
|
|
m_szMatchStatTxt.GetForModify()[ 0 ] = 0;
|
|
m_nTournamentPredictionsPct = 0;
|
|
|
|
HOOK_MESSAGE( SendPlayerItemDrops );
|
|
HOOK_MESSAGE( SendPlayerItemFound );
|
|
m_bMarkClientStopRecordAtRoundEnd = false;
|
|
|
|
}
|
|
|
|
CCSGameRules::~CCSGameRules()
|
|
{
|
|
ResetCasterConvars();
|
|
|
|
for ( int j = 0; j < MAX_TOURNAMENT_ACTIVE_CASTER_COUNT; ++ j )
|
|
{
|
|
m_arrTournamentActiveCasterAccounts.Set( j, 0 );
|
|
}
|
|
}
|
|
|
|
// CLIENT
|
|
bool __MsgFunc_SendPlayerItemDrops( const CCSUsrMsg_SendPlayerItemDrops &msg )
|
|
{
|
|
//IViewPortPanel* panel = GetViewPortInterface()->FindPanelByName( PANEL_SCOREBOARD );
|
|
if ( CSGameRules() )
|
|
{
|
|
CSGameRules()->ClearItemsDroppedDuringMatch();
|
|
for ( int i = 0; i < msg.entity_updates_size(); i ++ )
|
|
{
|
|
const CEconItemPreviewDataBlock &update = msg.entity_updates(i);
|
|
CSGameRules()->RecordPlayerItemDrop( update );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCSGameRules::MarkClientStopRecordAtRoundEnd( bool bStop )
|
|
{
|
|
m_bMarkClientStopRecordAtRoundEnd = bStop;
|
|
}
|
|
|
|
void CCSGameRules::OpenBuyMenu( int nPlayerID )
|
|
{
|
|
// skip buy menu during demo playback
|
|
if ( engine->IsPlayingDemo() || engine->IsPlayingTimeDemo() )
|
|
return;
|
|
|
|
GetViewPortInterface()->ShowPanel( PANEL_BUY, true );
|
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "buymenu_open" );
|
|
if ( pEvent )
|
|
{
|
|
pEvent->SetInt("userid", nPlayerID );
|
|
gameeventmanager->FireEventClientSide( pEvent );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::CloseBuyMenu( int nPlayerID )
|
|
{
|
|
// skip buy menu during demo playback
|
|
if ( engine->IsPlayingDemo() || engine->IsPlayingTimeDemo() )
|
|
return;
|
|
|
|
GetViewPortInterface()->ShowPanel( PANEL_BUY, false );
|
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "buymenu_close" );
|
|
if ( pEvent )
|
|
{
|
|
pEvent->SetInt("userid", nPlayerID );
|
|
gameeventmanager->FireEventClientSide( pEvent );
|
|
}
|
|
|
|
// update the inventory
|
|
CCSPlayer *pPlayer = static_cast<CCSPlayer*>( UTIL_PlayerByIndex(engine->GetPlayerForUserID( nPlayerID )) );
|
|
C_WeaponCSBase *pWeapon = (pPlayer && pPlayer->IsLocalPlayer()) ? pPlayer->GetActiveCSWeapon() : NULL ;
|
|
if ( pWeapon )
|
|
{
|
|
CBaseHudWeaponSelection *pHudSelection = GetHudWeaponSelection();
|
|
if ( pHudSelection )
|
|
{
|
|
pHudSelection->OnWeaponSwitch( pWeapon );
|
|
}
|
|
}
|
|
}
|
|
|
|
const wchar_t* CCSGameRules::GetFriendlyMapName( const char* szShortName )
|
|
{
|
|
// Look up the nice version of the name.
|
|
char szToken[MAX_MAP_NAME+10+1]; // includes prefix size
|
|
szToken[0] = '\0';
|
|
V_snprintf( szToken, ARRAYSIZE( szToken ), "#SFUI_Map_%s", szShortName );
|
|
wchar_t* szTranslated = g_pVGuiLocalize->Find( szToken );
|
|
if ( szTranslated )
|
|
return szTranslated;
|
|
|
|
char szPath[MAX_PATH];
|
|
V_strcpy_safe( szPath, szShortName );
|
|
V_FixSlashes( szPath, '/' ); // internal path strings use forward slashes, make sure we compare like that.
|
|
if ( V_strstr( szPath, "workshop/" ) )
|
|
{
|
|
PublishedFileId_t ullId = GetMapIDFromMapPath( szPath );
|
|
const PublishedFileInfo_t *pInfo = WorkshopManager().GetPublishedFileInfoByID( ullId );
|
|
static wchar_t wszMapName[128];
|
|
if ( pInfo )
|
|
{
|
|
g_pVGuiLocalize->ConvertANSIToUnicode(pInfo->m_rgchTitle, wszMapName, sizeof(wszMapName));
|
|
return wszMapName;
|
|
}
|
|
else
|
|
{
|
|
g_pVGuiLocalize->ConvertANSIToUnicode( V_GetFileName( szShortName ), wszMapName, sizeof(wszMapName));
|
|
return wszMapName;
|
|
}
|
|
}
|
|
|
|
static wchar_t wszMapName[128];
|
|
g_pVGuiLocalize->ConvertANSIToUnicode(szShortName, wszMapName, sizeof(wszMapName));
|
|
return wszMapName;
|
|
}
|
|
|
|
bool CCSGameRules::GetFriendlyMapNameToken( const char* szShortName, char* szOutBuffer, int nBuffSize )
|
|
{
|
|
// Create the nice version of the name.
|
|
CreateFriendlyMapNameToken( szShortName, szOutBuffer, nBuffSize );
|
|
|
|
if ( g_pVGuiLocalize->Find( szOutBuffer ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
char const * CCSGameRules::GetTournamentEventName() const
|
|
{
|
|
const char *sz = m_szTournamentEventName.Get();
|
|
if ( sz && *sz && ( sz[0] != '#' ) )
|
|
{
|
|
// Localize it:
|
|
static char const * const arrEvents[] = { ""
|
|
,"The 2013 DreamHack SteelSeries CS:GO Championship"
|
|
,"The Valve DPR Championship"
|
|
,"The 2014 EMS One Katowice CS:GO Championship"
|
|
,"ESL One Cologne 2014 CS:GO Championship"
|
|
,"The 2014 DreamHack CS:GO Championship"
|
|
,"ESL One Katowice 2015 CS:GO Championship"
|
|
,"ESL One Cologne 2015 CS:GO Championship"
|
|
,"DreamHack Cluj-Napoca 2015 CS:GO Championship"
|
|
,"MLG Columbus 2016 CS:GO Championship"
|
|
,"ESL One Cologne 2016 CS:GO Championship"
|
|
,"ELEAGUE Atlanta 2017 CS:GO Championship"
|
|
};
|
|
for ( int k = Q_ARRAYSIZE( arrEvents ) - 1; k > 0; -- k )
|
|
{
|
|
if ( !V_strcmp( sz, arrEvents[k] ) )
|
|
{
|
|
V_snprintf( const_cast< char * >( sz ), MAX_PATH, "#CSGO_Tournament_Event_Name_%d", k );
|
|
return sz;
|
|
}
|
|
}
|
|
}
|
|
return m_szTournamentEventName;
|
|
}
|
|
|
|
char const * CCSGameRules::GetTournamentEventStage() const
|
|
{
|
|
const char *sz = m_szTournamentEventStage.Get();
|
|
if ( sz && *sz && ( sz[ 0 ] != '#' ) )
|
|
{
|
|
// Localize it:
|
|
static char const * const arrEvents[] = { ""
|
|
,"Exhibition"
|
|
,"Group Stage | First Stage"
|
|
,"BYOC"
|
|
,"Valve Pre-Event Test"
|
|
,"Match 1 of 3 | Quarterfinal"
|
|
,"Match 2 of 3 | Quarterfinal"
|
|
,"Match 3 of 3 | Quarterfinal"
|
|
,"Match 1 of 3 | Semifinal"
|
|
,"Match 2 of 3 | Semifinal"
|
|
,"Match 3 of 3 | Semifinal"
|
|
,"Match 1 of 3 | Grand Final"
|
|
,"Match 2 of 3 | Grand Final"
|
|
,"Match 3 of 3 | Grand Final"
|
|
,"All-Star"
|
|
,"Group Stage | Winners Match"
|
|
,"Group Stage | Elimination Match"
|
|
,"Group Stage | Decider Match"
|
|
,"Qualification"
|
|
,"Match 2 of 3 | Qualification"
|
|
,"Match 3 of 3 | Qualification"
|
|
,"Group Stage | Decider Match 1 of 3"
|
|
,"Group Stage | Decider Match 2 of 3"
|
|
,"Group Stage | Decider Match 3 of 3"
|
|
,"Group Stage | Second Stage (Upper Pool)"
|
|
,"Group Stage | Second Stage (Lower Pool)"
|
|
,"Group Stage | Third Stage"
|
|
};
|
|
for ( int k = Q_ARRAYSIZE( arrEvents ) - 1; k > 0; --k )
|
|
{
|
|
if ( !V_strcmp( sz, arrEvents[ k ] ) )
|
|
{
|
|
V_snprintf( const_cast< char * >( sz ), MAX_PATH, "#CSGO_Tournament_Event_Stage_Display_%d", k );
|
|
return sz;
|
|
}
|
|
}
|
|
}
|
|
return m_szTournamentEventStage;
|
|
}
|
|
|
|
//
|
|
// all of this is here to pick the first caster in the active caster list by default
|
|
// later when we have a "pick caster" dialog then this can go away
|
|
//
|
|
extern ConVar spec_autodirector_cameraman;
|
|
extern ConVar spec_cameraman_ui;
|
|
extern ConVar spec_cameraman_xray;
|
|
|
|
void CCSGameRules::RecvProxy_TournamentActiveCasterAccounts( const CRecvProxyData *pData, void *pStruct, void *pOut )
|
|
{
|
|
RecvProxy_Int32ToInt32( pData, pStruct, pOut );
|
|
|
|
// set convars here
|
|
if ( spec_autodirector_cameraman.GetInt() == -1 && CSGameRules() && CSGameRules()->m_arrTournamentActiveCasterAccounts[ 0 ] != 0 )
|
|
{
|
|
int casterID = ( int ) ( CSGameRules()->m_arrTournamentActiveCasterAccounts[ 0 ] );
|
|
spec_autodirector_cameraman.SetValue( casterID );
|
|
spec_cameraman_ui.SetValue( casterID );
|
|
spec_cameraman_xray.SetValue( casterID );
|
|
engine->SetVoiceCasterID( CSGameRules()->m_arrTournamentActiveCasterAccounts[ 0 ] );
|
|
}
|
|
}
|
|
|
|
void CCSGameRules::ResetCasterConvars()
|
|
{
|
|
spec_autodirector_cameraman.SetValue( -1 );
|
|
spec_cameraman_ui.SetValue( 0 );
|
|
spec_cameraman_xray.SetValue( 0 );
|
|
engine->SetVoiceCasterID( 0 );
|
|
}
|
|
|
|
#endif
|
|
|
|
CEconQuestDefinition* CCSGameRules::GetActiveAssassinationQuest( void ) const
|
|
{
|
|
if ( IsPlayingCooperativeGametype() )
|
|
return NULL;
|
|
|
|
// Skip the map lookup for invlaid quest id
|
|
if ( !m_iActiveAssassinationTargetMissionID )
|
|
return NULL;
|
|
else
|
|
return GetItemSchema()->GetQuestDefinition( m_iActiveAssassinationTargetMissionID );
|
|
}
|
|
|
|
float CCSGameRules::GetCMMItemDropRevealDuration()
|
|
{
|
|
if ( m_flCMMItemDropRevealEndTime == 0 && m_ItemsPtrDroppedDuringMatch.Count() <= 0 )
|
|
return 2;
|
|
|
|
if ( m_flCMMItemDropRevealEndTime == 0 && m_flCMMItemDropRevealStartTime != 0 )
|
|
{
|
|
float flItemDropTime = 2.0f;
|
|
for ( int i = 0; i < m_ItemsPtrDroppedDuringMatch.Count(); i++ )
|
|
{
|
|
switch ( m_ItemsPtrDroppedDuringMatch[i]->rarity() )
|
|
{
|
|
case 6:
|
|
case 5:
|
|
{
|
|
flItemDropTime += sv_endmatch_item_drop_interval_ancient.GetFloat();
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
flItemDropTime += sv_endmatch_item_drop_interval_legendary.GetFloat();
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
flItemDropTime += sv_endmatch_item_drop_interval_mythical.GetFloat();
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
flItemDropTime += sv_endmatch_item_drop_interval_rare.GetFloat();
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
flItemDropTime += sv_endmatch_item_drop_interval.GetFloat();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_flCMMItemDropRevealEndTime = (m_flCMMItemDropRevealStartTime + flItemDropTime);
|
|
}
|
|
|
|
return MAX( 2, (m_flCMMItemDropRevealEndTime - m_flCMMItemDropRevealStartTime) );
|
|
}
|
|
|
|
void CCSGameRules::CreateFriendlyMapNameToken( const char* szShortName, char* szOutBuffer, int nBuffSize )
|
|
{
|
|
// must be able to fit the token below
|
|
Assert( nBuffSize >= MAX_MAP_NAME+10+1 );
|
|
|
|
// Create the nice version of the name.
|
|
// right now we blindly create the token because we can assume (on consoles) that we don't have any maps
|
|
// that we don't know about. For PC, we'll need to fix this so all checks will happen on the client
|
|
V_snprintf( szOutBuffer, nBuffSize, "#SFUI_Map_%s", szShortName );
|
|
}
|
|
|
|
const char *CCSGameRules::GetDefaultTeamName( int nTeam )
|
|
{
|
|
Assert( nTeam >= 0 && nTeam < ARRAYSIZE( sTeamNames ) );
|
|
|
|
return sTeamNames[nTeam];
|
|
}
|
|
|
|
void CCSGameRules::AddGunGameWeapon( const char* pWeaponName, int nNumKillsToUpgrade, int nTeamID )
|
|
{
|
|
if ( !pWeaponName )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Assert( nNumKillsToUpgrade > 0 );
|
|
if ( nNumKillsToUpgrade <= 0 )
|
|
{
|
|
Warning( "CCSGameRules: Invalid number of kills-to-upgrade (%d) for weapon %s.\n", nNumKillsToUpgrade, pWeaponName );
|
|
nNumKillsToUpgrade = 1;
|
|
}
|
|
|
|
char weaponName[MAX_WEAPON_STRING];
|
|
weaponName[0] = '\0';
|
|
V_snprintf( weaponName, sizeof( weaponName ), "weapon_%s", pWeaponName );
|
|
|
|
int nWeaponID = 0;
|
|
|
|
// Try to get the ID from the item name
|
|
const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( weaponName );
|
|
if ( pDef )
|
|
{
|
|
nWeaponID = pDef->GetDefinitionIndex();
|
|
}
|
|
|
|
if ( nWeaponID == 0 )
|
|
{
|
|
// Fall back to the older weapon id system for things like knifegg
|
|
nWeaponID = WeaponIdFromString( weaponName );
|
|
}
|
|
|
|
if ( nWeaponID != WEAPON_NONE )
|
|
{
|
|
if ( nTeamID == TEAM_CT )
|
|
{
|
|
m_GGProgressiveWeaponOrderCT.Set( m_iNumGunGameProgressiveWeaponsCT, nWeaponID );
|
|
m_GGProgressiveWeaponKillUpgradeOrderCT.Set( m_iNumGunGameProgressiveWeaponsCT, nNumKillsToUpgrade );
|
|
m_iNumGunGameProgressiveWeaponsCT++;
|
|
}
|
|
else if ( nTeamID == TEAM_TERRORIST )
|
|
{
|
|
m_GGProgressiveWeaponOrderT.Set( m_iNumGunGameProgressiveWeaponsT, nWeaponID );
|
|
m_GGProgressiveWeaponKillUpgradeOrderT.Set( m_iNumGunGameProgressiveWeaponsT, nNumKillsToUpgrade );
|
|
m_iNumGunGameProgressiveWeaponsT++;
|
|
}
|
|
|
|
#if defined ( GAME_DLL )
|
|
// PERF: Force creation of instance baselines so we don't take the hit upon creation.
|
|
// We're doing this because we get a huge network spike upon player death which causes hitches on PS3.
|
|
// Pre-creating baselines offloads a bit of that spike.
|
|
UTIL_EnsureInstanceBaseline( weaponName );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
Warning( "CCSGameRules::AddGunGameWeapon: encountered an unknown weapon \"%s\".\n", pWeaponName );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Set up the convars and data associated with the current game type and mode
|
|
//-----------------------------------------------------------------------------
|
|
void CCSGameRules::InitializeGameTypeAndMode( void )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
bool isMultiplayer = !IsPlayingOffline();
|
|
|
|
const char* szMapNameFull = STRING(gpGlobals->mapname);
|
|
|
|
|
|
///////////// load kv values from map sidecar file ( map.kv )
|
|
////////
|
|
|
|
char filename[ MAX_PATH ];
|
|
V_StripExtension( V_UnqualifiedFileName( STRING( gpGlobals->mapname ) ), filename, MAX_PATH );
|
|
|
|
uint32 dwRichPresenceContext = 0xFFFF;
|
|
if ( !g_pGameTypes->GetMapInfo( filename, dwRichPresenceContext ) )
|
|
{
|
|
|
|
char kvFilename[ MAX_PATH ];
|
|
|
|
bool bLoadSideCar = true;
|
|
|
|
V_snprintf( kvFilename, sizeof( kvFilename ), "maps/%s.kv", filename );
|
|
|
|
if ( !g_pFullFileSystem->FileExists( kvFilename ) )
|
|
bLoadSideCar = false;
|
|
|
|
// Load the Map sidecar entry
|
|
|
|
if ( bLoadSideCar )
|
|
{
|
|
KeyValues *pkvMap = new KeyValues( "Map" );
|
|
KeyValues::AutoDelete autodelete_kvMap( pkvMap );
|
|
|
|
if ( pkvMap->LoadFromFile( g_pFullFileSystem, kvFilename ) )
|
|
{
|
|
g_pGameTypes->LoadMapEntry( pkvMap );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Failed to load %s\n", kvFilename );
|
|
}
|
|
}
|
|
}
|
|
/////
|
|
///////////////////
|
|
|
|
g_pGameTypes->CheckShouldSetDefaultGameModeAndType( szMapNameFull );
|
|
|
|
// Set the mode convars
|
|
if ( g_pGameTypes->ApplyConvarsForCurrentMode( isMultiplayer ) )
|
|
{
|
|
// Add the gun game weapons.
|
|
if ( IsPlayingGunGameProgressive() && mp_ggprogressive_use_random_weapons.GetBool() )
|
|
{
|
|
// this is where we build the list of GG progressive weapons
|
|
// collect all of the weapons here
|
|
CUtlVector< GGWeaponAliasName > pSMGs;
|
|
for ( int i=GGLIST_SMGS_START; i<(GGLIST_SMGS_LAST+1); i++ )
|
|
pSMGs.AddToTail(ggWeaponAliasNameList[i]);
|
|
CUtlVector< GGWeaponAliasName > pShotguns;
|
|
for ( int i=GGLIST_SGS_START; i<(GGLIST_SGS_LAST+1); i++ )
|
|
pShotguns.AddToTail(ggWeaponAliasNameList[i]);
|
|
CUtlVector< GGWeaponAliasName > pRifles;
|
|
for ( int i=GGLIST_RIFLES_START; i<(GGLIST_RIFLES_LAST+1); i++ )
|
|
pRifles.AddToTail(ggWeaponAliasNameList[i]);
|
|
CUtlVector< GGWeaponAliasName > pSnipers;
|
|
for ( int i=GGLIST_SNIPERS_START; i<(GGLIST_SNIPERS_LAST+1); i++ )
|
|
pSnipers.AddToTail(ggWeaponAliasNameList[i]);
|
|
CUtlVector< GGWeaponAliasName > pMGs;
|
|
for ( int i=GGLIST_MGS_START; i<(GGLIST_MGS_LAST+1); i++ )
|
|
pMGs.AddToTail(ggWeaponAliasNameList[i]);
|
|
CUtlVector< GGWeaponAliasName > pPistols;
|
|
for ( int i=GGLIST_PISTOLS_START; i<(GGLIST_PISTOLS_LAST+1); i++ )
|
|
pPistols.AddToTail(ggWeaponAliasNameList[i]);
|
|
|
|
int nNumSMGs = 3;
|
|
int nNumRifles = 4;
|
|
int nNumShotguns = 2;
|
|
int nNumSnipers = 2;
|
|
int nNumMGs = 1;
|
|
int nNumPistols = 4;
|
|
// this should total 16 weapons
|
|
|
|
int nKillsNeeded = MAX( 1, mp_ggprogressive_random_weapon_kills_needed.GetInt() );
|
|
|
|
// now pick a random one from the list we created above for each category
|
|
CUtlVector< GGWeaponAliasName > pWeaponProgression;
|
|
for ( int i=0; i<nNumSMGs; i++ )
|
|
{
|
|
int nPick = RandomInt( 0, pSMGs.Count()-1 );
|
|
pWeaponProgression.AddToTail(pSMGs[nPick]);
|
|
pSMGs.FastRemove( nPick );
|
|
}
|
|
for ( int i=0; i<nNumRifles; i++ )
|
|
{
|
|
int nPick = RandomInt( 0, pRifles.Count()-1 );
|
|
pWeaponProgression.AddToTail(pRifles[nPick]);
|
|
pRifles.FastRemove( nPick );
|
|
}
|
|
for ( int i=0; i<nNumShotguns; i++ )
|
|
{
|
|
int nPick = RandomInt( 0, pShotguns.Count()-1 );
|
|
pWeaponProgression.AddToTail(pShotguns[nPick]);
|
|
pShotguns.FastRemove( nPick );
|
|
}
|
|
for ( int i=0; i<nNumSnipers; i++ )
|
|
{
|
|
int nPick = RandomInt( 0, pSnipers.Count()-1 );
|
|
pWeaponProgression.AddToTail(pSnipers[nPick]);
|
|
pSnipers.FastRemove( nPick );
|
|
}
|
|
for ( int i=0; i<nNumMGs; i++ )
|
|
{
|
|
int nPick = RandomInt( 0, pMGs.Count()-1 );
|
|
pWeaponProgression.AddToTail(pMGs[nPick]);
|
|
pMGs.FastRemove( nPick );
|
|
}
|
|
for ( int i=0; i<nNumPistols; i++ )
|
|
{
|
|
int nPick = RandomInt( 0, pPistols.Count()-1 );
|
|
pWeaponProgression.AddToTail(pPistols[nPick]);
|
|
pPistols.FastRemove( nPick );
|
|
}
|
|
|
|
// go through the list we build and add them to the final list that will get used in the game
|
|
FOR_EACH_VEC( pWeaponProgression, iWeaponProgression )
|
|
{
|
|
const GGWeaponAliasName &wp = (pWeaponProgression)[iWeaponProgression];
|
|
AddGunGameWeapon( wp.aliasName, nKillsNeeded, TEAM_CT );
|
|
AddGunGameWeapon( wp.aliasName, nKillsNeeded, TEAM_TERRORIST );
|
|
}
|
|
|
|
// add the knife manually
|
|
AddGunGameWeapon( "knifegg", 1, TEAM_CT );
|
|
AddGunGameWeapon( "knifegg", 1, TEAM_TERRORIST );
|
|
}
|
|
// Add the gun game weapons.
|
|
else if ( IsPlayingGunGameTRBomb() || IsPlayingGunGameProgressive() )
|
|
{
|
|
const CUtlVector< IGameTypes::WeaponProgression > *pWeaponProgressionCT = g_pGameTypes->GetWeaponProgressionForCurrentModeCT();
|
|
if ( pWeaponProgressionCT )
|
|
{
|
|
FOR_EACH_VEC( *pWeaponProgressionCT, iWeaponProgression )
|
|
{
|
|
const IGameTypes::WeaponProgression &wp = (*pWeaponProgressionCT)[iWeaponProgression];
|
|
AddGunGameWeapon( wp.m_Name, wp.m_Kills, TEAM_CT );
|
|
}
|
|
}
|
|
|
|
const CUtlVector< IGameTypes::WeaponProgression > *pWeaponProgressionT = g_pGameTypes->GetWeaponProgressionForCurrentModeT();
|
|
if ( pWeaponProgressionT )
|
|
{
|
|
FOR_EACH_VEC( * pWeaponProgressionT, iWeaponProgression )
|
|
{
|
|
const IGameTypes::WeaponProgression &wp = (* pWeaponProgressionT )[iWeaponProgression];
|
|
AddGunGameWeapon( wp.m_Name, wp.m_Kills, TEAM_TERRORIST );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( CSGameRules()->HasHalfTime() )
|
|
{
|
|
m_match.SetPhase( GAMEPHASE_PLAYING_FIRST_HALF );
|
|
}
|
|
else
|
|
{
|
|
m_match.SetPhase( GAMEPHASE_PLAYING_STANDARD );
|
|
}
|
|
|
|
// Set the map convars
|
|
ConVarRef host_map( "host_map" );
|
|
g_pGameTypes->ApplyConvarsForMap( host_map.GetString(), engine->IsDedicatedServer() || isMultiplayer );
|
|
|
|
if ( CSGameRules()->IsPlayingCooperativeGametype() )
|
|
{
|
|
uint32 unQuestID = MatchmakingGameTypeToMapGroup(CCSGameRules::sm_QueuedServerReservation.game_type() );
|
|
const CEconQuestDefinition *pQuest = GetItemSchema()->GetQuestDefinition( unQuestID );
|
|
if ( pQuest && !StringIsEmpty ( pQuest->GetQuestConVars() ) )
|
|
{
|
|
m_iActiveAssassinationTargetMissionID = unQuestID;
|
|
engine->ServerCommand( CFmtStr( "%s\n", pQuest->GetQuestConVars() ) );
|
|
engine->ServerExecute();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if ( CSTRIKE_DEMO_PRESSBUILD || CSTRIKE_E3_BUILD )
|
|
|
|
ConVarRef bot_quota( "bot_quota" );
|
|
bot_quota.SetValue( 8 );
|
|
|
|
ConVarRef bot_difficulty( "bot_difficulty" );
|
|
bot_difficulty.SetValue( 0 );
|
|
|
|
#endif
|
|
|
|
// this allows the weapon recoil tables to incorporate changes made to convars before the game launches
|
|
g_WeaponDatabase.RefreshAllWeapons();
|
|
}
|
|
|
|
int CCSGameRules::GetNumProgressiveGunGameWeapons( int nTeamID ) const
|
|
{
|
|
if ( nTeamID == TEAM_CT )
|
|
{
|
|
return m_iNumGunGameProgressiveWeaponsCT;
|
|
}
|
|
else
|
|
{
|
|
return m_iNumGunGameProgressiveWeaponsT;
|
|
}
|
|
}
|
|
|
|
int CCSGameRules::GetGunGameTRBonusGrenade( CCSPlayer *pPlayer )
|
|
{
|
|
if ( !pPlayer )
|
|
return 0;
|
|
|
|
int nNumTRKillPoints = pPlayer->GetNumGunGameTRKillPoints();
|
|
|
|
if ( nNumTRKillPoints >= mp_ggtr_bomb_pts_for_molotov.GetInt() )
|
|
{
|
|
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
|
return WEAPON_MOLOTOV;
|
|
else
|
|
return WEAPON_INCGRENADE;
|
|
}
|
|
else if ( nNumTRKillPoints >= mp_ggtr_bomb_pts_for_flash.GetInt() )
|
|
{
|
|
return WEAPON_FLASHBANG;
|
|
}
|
|
else if ( nNumTRKillPoints >= mp_ggtr_bomb_pts_for_he.GetInt() )
|
|
{
|
|
return WEAPON_HEGRENADE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool CCSGameRules::IsIntermission( void ) const
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
return m_flIntermissionStartTime + GetIntermissionDuration() > gpGlobals->curtime;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
int CCSGameRules::GetMaxSpectatorSlots( void ) const
|
|
{
|
|
return m_iSpectatorSlotCount;
|
|
}
|
|
|
|
int CCSGameRules::GetMaxPlayers()
|
|
{
|
|
if ( sv_competitive_official_5v5.GetInt() )
|
|
return 10;
|
|
|
|
#ifdef CLIENT_DLL
|
|
if ( engine->IsPlayingDemo() || !engine->IsConnected() )
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
return MIN( g_pGameTypes->GetCurrentServerNumSlots(), 24 );
|
|
}
|
|
|
|
void CCSGameRules::CoopResetRoundStartTime( void )
|
|
{
|
|
m_fRoundStartTime.GetForModify() = gpGlobals->curtime;
|
|
|
|
#ifdef GAME_DLL
|
|
m_coopPlayersInDeploymentZone = false;
|
|
|
|
if ( IsPlayingCoopMission() )
|
|
{
|
|
// Reset certain player stats when this happens
|
|
FOR_EACH_MAP( m_mapQueuedMatchmakingPlayersData, idxQueuedPlayer )
|
|
{
|
|
CQMMPlayerData_t &qmm = *m_mapQueuedMatchmakingPlayersData.Element( idxQueuedPlayer );
|
|
qmm.m_numHealthPointsRemovedTotal = 0;
|
|
qmm.m_numHealthPointsDealtTotal = 0;
|
|
qmm.m_numShotsFiredTotal = 0;
|
|
qmm.m_numShotsOnTargetTotal = 0;
|
|
}
|
|
|
|
// now the actual round is starting, adjust the duration here
|
|
m_iRoundTime.GetForModify() = int( mp_roundtime.GetFloat() * 60 );
|
|
}
|
|
|
|
// we also want to refresh the difficulty of the bots here
|
|
CTeam *pTeam = GetGlobalTeam( TEAM_TERRORIST );
|
|
CUtlVector < CCSBot* > vecBotsOnTeam;
|
|
if ( pTeam->GetBotMembers( &vecBotsOnTeam ) )
|
|
{
|
|
FOR_EACH_VEC( vecBotsOnTeam, i )
|
|
{
|
|
vecBotsOnTeam[i]->CoopInitialize();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CCSGameRules::CoopGiveC4sToCTs( int nC4sToGive )
|
|
{
|
|
#ifdef GAME_DLL
|
|
CTeam *pTeam = GetGlobalTeam( TEAM_CT );
|
|
CUtlVector < CCSPlayer* > vecPlayers;
|
|
int nC4s = nC4sToGive;
|
|
if ( pTeam->GetHumanMembers( &vecPlayers ) )
|
|
{
|
|
FOR_EACH_VEC( vecPlayers, i )
|
|
{
|
|
if ( vecPlayers[i]->Weapon_OwnsThisType( WEAPON_C4_CLASSNAME ) )
|
|
nC4s--;
|
|
}
|
|
|
|
// don't give more C4s than players
|
|
if ( nC4s == 0 )
|
|
return;
|
|
|
|
FOR_EACH_VEC( vecPlayers, i )
|
|
{
|
|
if ( vecPlayers[i]->IsAlive() && !vecPlayers[i]->Weapon_OwnsThisType( WEAPON_C4_CLASSNAME ) )
|
|
vecPlayers[i]->GiveNamedItem( WEAPON_C4_CLASSNAME );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int CCSGameRules::GetNumWinsToClinch() const
|
|
{
|
|
int iNumWinsToClinch = (mp_maxrounds.GetInt() > 0 && mp_match_can_clinch.GetBool()) ? ( mp_maxrounds.GetInt() / 2 ) + 1 + GetOvertimePlaying() * ( mp_overtime_maxrounds.GetInt() / 2 ) : -1;
|
|
return iNumWinsToClinch;
|
|
}
|
|
|
|
bool CCSGameRules::IsLastRoundOfMatch() const
|
|
{
|
|
bool bLastRound = mp_maxrounds.GetInt() > 0 ? ( GetTotalRoundsPlayed() == ( mp_maxrounds.GetInt()-1 + GetOvertimePlaying()*mp_overtime_maxrounds.GetInt() ) ) : false;
|
|
return bLastRound;
|
|
}
|
|
|
|
bool CCSGameRules::IsMatchPoint() const
|
|
{
|
|
int iNumWinsToClinch = GetNumWinsToClinch();
|
|
bool bMatchPoint = false;
|
|
#ifdef CLIENT_DLL
|
|
if ( GetGamePhase() != GAMEPHASE_PLAYING_FIRST_HALF )
|
|
{
|
|
C_Team *pTerrorists = GetGlobalTeam( TEAM_TERRORIST );
|
|
C_Team *pCTs = GetGlobalTeam( TEAM_CT );
|
|
bMatchPoint = ( pCTs && ( pCTs->Get_Score() == iNumWinsToClinch-1 ) ) || ( pTerrorists && ( pTerrorists->Get_Score() == iNumWinsToClinch-1 ) );
|
|
}
|
|
#else
|
|
if ( m_match.GetPhase() != GAMEPHASE_PLAYING_FIRST_HALF )
|
|
{
|
|
bMatchPoint = ( m_match.GetCTScore() == iNumWinsToClinch-1 || m_match.GetTerroristScore() == iNumWinsToClinch-1);
|
|
}
|
|
#endif
|
|
return bMatchPoint;
|
|
}
|
|
|
|
// AreTeamsPlayingSwitchedSides() -- will return true when match is in second half, or in the half of overtime period where teams are switched.
|
|
// Overtime logic is as follows: TeamA plays CTs as first half of regulation, then Ts as second half of regulation,
|
|
// then if tied in regulation continues to play Ts as first half of 1st overtime, then switches to CTs for second half of 1st overtime,
|
|
// then if still tied after 1st OT they continue to play CTs as first half of 2nd overtime, then switch to Ts for second half of 2nd overtime,
|
|
// then if still tied after 2nd OT they continue to play Ts as first half of 3rd overtime, then switch to CTs for second half of 3rd overtime,
|
|
// and so on until the match determines a winner.
|
|
// So AreTeamsPlayingSwitchedSides will return true when TeamA is playing T-side and will return false when TeamA plays CT-side as they started match on CT
|
|
// in scenario outlined above.
|
|
bool CCSGameRules::AreTeamsPlayingSwitchedSides() const
|
|
{
|
|
if ( !GetOvertimePlaying() )
|
|
{
|
|
switch ( GetGamePhase() )
|
|
{
|
|
case GAMEPHASE_PLAYING_SECOND_HALF:
|
|
return true;
|
|
case GAMEPHASE_MATCH_ENDED:
|
|
return HasHalfTime() && ( GetTotalRoundsPlayed() > ( mp_maxrounds.GetInt() / 2 ) );
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ( GetGamePhase() )
|
|
{
|
|
case GAMEPHASE_PLAYING_SECOND_HALF:
|
|
// Playing 2nd half of 2nd half of every even OT, e.g. second OT, will result in switched teams
|
|
return ( GetOvertimePlaying() % 2 ) ? false : true;
|
|
case GAMEPHASE_MATCH_ENDED:
|
|
{
|
|
bool bEndedInSecondHalfOfOvertime = HasHalfTime() &&
|
|
( GetTotalRoundsPlayed() > mp_maxrounds.GetInt() + ( 2*GetOvertimePlaying() - 1 ) * ( mp_overtime_maxrounds.GetInt() / 2 ) );
|
|
if ( GetOvertimePlaying() % 2 )
|
|
bEndedInSecondHalfOfOvertime = !bEndedInSecondHalfOfOvertime;
|
|
return bEndedInSecondHalfOfOvertime;
|
|
}
|
|
case GAMEPHASE_HALFTIME:
|
|
{
|
|
// halftime can also be at the end of regulation or at the end of both OT halves, in this case the overtime number has
|
|
// already been incremented into the next overtime
|
|
bool bSecondHalfOfOvertime = HasHalfTime() &&
|
|
( GetTotalRoundsPlayed() <= ( mp_maxrounds.GetInt() + ( GetOvertimePlaying() - 1 )*mp_overtime_maxrounds.GetInt() ) );
|
|
int nOvertimeInWhichHalftimeIsActuallyReached = GetOvertimePlaying();
|
|
if ( bSecondHalfOfOvertime )
|
|
-- nOvertimeInWhichHalftimeIsActuallyReached; // this is the case when we already advanced the OT index and wait in intermission
|
|
if ( nOvertimeInWhichHalftimeIsActuallyReached % 2 )
|
|
bSecondHalfOfOvertime = !bSecondHalfOfOvertime;
|
|
return bSecondHalfOfOvertime;
|
|
}
|
|
break;
|
|
default:
|
|
// Playing 1st half, opposite of GAMEPHASE_PLAYING_SECOND_HALF state coded above
|
|
return ( GetOvertimePlaying() % 2 ) ? true : false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// music selection
|
|
#ifdef CLIENT_DLL
|
|
|
|
const char *musicTypeStrings[] =
|
|
{
|
|
"NONE",
|
|
"Music.StartRound_GG",
|
|
"Music.StartRound",
|
|
"Music.StartAction",
|
|
"Music.DeathCam",
|
|
"Music.BombPlanted",
|
|
"Music.BombTenSecCount",
|
|
"Music.TenSecCount",
|
|
"Music.WonRound",
|
|
"Music.LostRound",
|
|
"Music.GotHostage",
|
|
"Music.MVPAnthem",
|
|
"Music.Selection",
|
|
"Musix.HalfTime",
|
|
};
|
|
|
|
void PlayMusicSelection( IRecipientFilter& filter, CsMusicType_t nMusicType , int nPlayerEntIndex /* = 0*/ , float flPreElapsedTime /*= 0.0*/ )
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// test for between rounds and block incoming events until in round
|
|
//
|
|
static bool bBetweenRound = false;
|
|
if( nMusicType == CSMUSIC_LOSTROUND || nMusicType == CSMUSIC_WONROUND || nMusicType == CSMUSIC_MVP || nMusicType == CSMUSIC_HALFTIME )
|
|
{
|
|
bBetweenRound = true;
|
|
}
|
|
else if( nMusicType == CSMUSIC_START || nMusicType == CSMUSIC_ACTION || nMusicType == CSMUSIC_STARTGG )
|
|
{
|
|
bBetweenRound = false;
|
|
}
|
|
if( bBetweenRound && ( nMusicType == CSMUSIC_BOMB || nMusicType == CSMUSIC_BOMBTEN || nMusicType == CSMUSIC_ROUNDTEN ))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const char *pEntry = musicTypeStrings[ nMusicType ];
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
// this is to be used in the case that no music pack is equipped, mimicing original csgo behavior
|
|
// music selection switches on team select screen and halftime
|
|
static int nUpdatedMusic = snd_music_selection.GetInt();
|
|
// hack cause halftime music is getting called twice for some reason
|
|
if( nMusicType == CSMUSIC_SELECTION || nMusicType == CSMUSIC_HALFTIME )
|
|
{
|
|
nUpdatedMusic = (nUpdatedMusic == 2 ) ? 1 : 2;
|
|
}
|
|
if( nMusicType == CSMUSIC_SELECTION || nMusicType == CSMUSIC_START )
|
|
{
|
|
snd_music_selection.SetValue( nUpdatedMusic );
|
|
}
|
|
char pMusicExtensionBuf[64];
|
|
V_sprintf_safe( pMusicExtensionBuf, "%s_%02i", "valve_csgo", snd_music_selection.GetInt() );
|
|
const char *pMusicExtension = pMusicExtensionBuf;
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// no halftime music in overwatch
|
|
if ( CDemoPlaybackParameters_t const *pParameters = engine->GetDemoPlaybackParameters() )
|
|
{
|
|
if(pParameters->m_bAnonymousPlayerIdentity && nMusicType == CSMUSIC_HALFTIME )
|
|
return; // or whatever is appropriate to not play the halftime music
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// is there delay from switching spectated player?
|
|
// ********* not currently used ******
|
|
// engine->SOSSetOpvarFloat( "spectateMusicDelay", flPreElapsedTime );
|
|
// if( nMusicType == CSMUSIC_START )
|
|
// {
|
|
// engine->SOSSetOpvarFloat("csgo_roundstart_time", gpGlobals->curtime );
|
|
// }
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// music source priority:
|
|
// passed in arg (if has music pack)> borrowed music > own music
|
|
|
|
int nPlayerIndex = 0;
|
|
|
|
// use a specified playerindex if passed in ( MVP )
|
|
if( ( nPlayerEntIndex != 0 ) && g_PR )
|
|
{
|
|
if ( g_PR->IsConnected( nPlayerEntIndex ) )
|
|
{
|
|
if ( C_CS_PlayerResource *cs_PR = dynamic_cast< C_CS_PlayerResource * >( g_PR ) )
|
|
{
|
|
uint32 unMusicID = cs_PR->GetMusicID( nPlayerEntIndex );
|
|
if( unMusicID > 1 )
|
|
nPlayerIndex = nPlayerEntIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the passed in player didn't give us a music id, check for a borrowed kit.
|
|
if ( !nPlayerIndex )
|
|
{
|
|
int nBorrowPlIndex = cl_borrow_music_from_player_index.GetInt();
|
|
|
|
if ( nBorrowPlIndex )
|
|
{
|
|
if ( g_PR->IsConnected( nBorrowPlIndex ) && !g_PR->IsFakePlayer( nBorrowPlIndex ) )
|
|
{
|
|
nPlayerIndex = nBorrowPlIndex;
|
|
}
|
|
else
|
|
{
|
|
cl_borrow_music_from_player_index.SetValue( cl_borrow_music_from_player_index.GetDefault() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise use our own
|
|
if ( !nPlayerIndex )
|
|
nPlayerIndex = GetLocalPlayerIndex();
|
|
|
|
{
|
|
uint32 unMusicID = 0;
|
|
|
|
if ( C_CS_PlayerResource *cs_PR = dynamic_cast< C_CS_PlayerResource * >( g_PR ) )
|
|
{
|
|
unMusicID = cs_PR->GetMusicID( nPlayerIndex );
|
|
}
|
|
if( unMusicID > 1 )
|
|
{
|
|
const CEconMusicDefinition *pMusicDef = GetItemSchema()->GetMusicDefinition(unMusicID);
|
|
if(pMusicDef)
|
|
pMusicExtension = pMusicDef->GetName();
|
|
}
|
|
|
|
}
|
|
|
|
if( pEntry )
|
|
{
|
|
char musicSelection[128];
|
|
int nExtLen = V_strlen( pMusicExtension );
|
|
int nStrLen = V_strlen( pEntry );
|
|
V_snprintf( musicSelection, nExtLen + nStrLen+2, "%s.%s", pEntry, pMusicExtension );
|
|
C_BaseEntity::EmitSound( filter, -1, musicSelection );
|
|
}
|
|
}
|
|
void PlayCustomDeathCamSelection( IRecipientFilter& filter, int nEntIndex, const char *pSoundEntry, int nDeathCamIndex )
|
|
{
|
|
char musicSelection[128];
|
|
V_snprintf( musicSelection, 128, "%s_%03i", pSoundEntry, nDeathCamIndex );
|
|
C_BaseEntity::EmitSound( filter, nEntIndex, musicSelection );
|
|
|
|
}
|
|
#endif
|
|
|
|
float CCSGameRules::CheckTotalSmokedLength( float flSmokeRadiusSq, Vector vecGrenadePos, Vector from, Vector to )
|
|
{
|
|
Vector sightDir = to - from;
|
|
float sightLength = sightDir.NormalizeInPlace();
|
|
|
|
// the detonation position is the actual position of the smoke grenade, but the smoke volume center is actually some number of units above that
|
|
Vector vecSmokeCenterOffset = Vector( 0, 0, 60 );
|
|
const Vector &smokeOrigin = vecGrenadePos + vecSmokeCenterOffset;
|
|
|
|
float flSmokeRadius = sqrt(flSmokeRadiusSq);
|
|
// if the start point or the end point is inside the radius of the smoke, then the line goes through the smoke
|
|
if ( (smokeOrigin - from).IsLengthLessThan( flSmokeRadius*0.95f ) || (smokeOrigin - to).IsLengthLessThan( flSmokeRadius ) )
|
|
return -1;
|
|
|
|
Vector toGrenade = smokeOrigin - from;
|
|
|
|
float alongDist = DotProduct( toGrenade, sightDir );
|
|
|
|
// compute closest point to grenade along line of sight ray
|
|
Vector close;
|
|
|
|
// constrain closest point to line segment
|
|
if (alongDist < 0.0f)
|
|
close = from;
|
|
else if (alongDist >= sightLength)
|
|
close = to;
|
|
else
|
|
close = from + sightDir * alongDist;
|
|
|
|
// if closest point is within smoke radius, the line overlaps the smoke cloud
|
|
Vector toClose = close - smokeOrigin;
|
|
float lengthSq = toClose.LengthSqr();
|
|
|
|
//float smokeRadius = (float)sqrt( flSmokeRadiusSq );
|
|
//NDebugOverlay::Sphere( smokeOrigin, smokeRadius, 0, 255, 0, true, 2.0f);
|
|
if (lengthSq < flSmokeRadiusSq)
|
|
{
|
|
// some portion of the ray intersects the cloud
|
|
|
|
// 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it
|
|
// determine the length of the chord that crosses the cloud
|
|
float smokedLength = 2.0f * (float)sqrt( flSmokeRadiusSq - lengthSq );
|
|
return smokedLength;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined( CLIENT_DLL )
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 == va_arg(vl,int) )
|
|
return;
|
|
}
|
|
va_end(vl);
|
|
|
|
if ( fValue < fMinValue || fValue > fMaxValue )
|
|
{
|
|
float fNewValue = MAX( MIN( fValue, fMaxValue ), fMinValue );
|
|
competitiveConvar.SetValue( fNewValue );
|
|
DevMsg( "Convar %s was out of range and forced to %.2f. Valid values are between %.2f and %.2f. To remove the restriction set sv_competitive_minspec 0 on the server.\n", szCvarName, fNewValue, fMinValue, fMaxValue );
|
|
}
|
|
else
|
|
{
|
|
competitiveConvar.SetValue( fValue );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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:
|
|
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 = 0;
|
|
s_pConVarBackups->deleteThis();
|
|
s_pConVarBackups = 0;
|
|
}
|
|
|
|
CCompetitiveCvarManager()
|
|
{
|
|
s_pCompetitiveConvars = 0;
|
|
s_pConVarBackups = 0;
|
|
}
|
|
private:
|
|
static CompetitiveConvarList_t* s_pCompetitiveConvars;
|
|
static KeyValues* s_pConVarBackups;
|
|
};
|
|
static CCompetitiveCvarManager *s_pCompetitiveCvarManager = new CCompetitiveCvarManager();
|
|
CCompetitiveCvarManager::CompetitiveConvarList_t* CCompetitiveCvarManager::s_pCompetitiveConvars = 0;
|
|
KeyValues* CCompetitiveCvarManager::s_pConVarBackups = 0;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 && flOldValue == 0.0f )
|
|
{
|
|
// Backup the values of each cvar and enforce new ones
|
|
CCompetitiveCvarManager::BackupAllConvars();
|
|
CCompetitiveCvarManager::EnforceRestrictionsOnAllConvars();
|
|
}
|
|
else if( pCvar->GetBool() == false && flOldValue != 0.0f )
|
|
{
|
|
// If sv_competitive_minspec is disabled, restore old client values
|
|
CCompetitiveCvarManager::RestoreAllOriginalValues();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static ConVar sv_competitive_minspec( "sv_competitive_minspec",
|
|
"1",
|
|
FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_RELEASE,
|
|
"Enable to force certain client convars to minimum/maximum values to help prevent competitive advantages."
|
|
#ifdef CLIENT_DLL
|
|
,sv_competitive_minspec_changed_f
|
|
#endif
|
|
);
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
#if defined( _GAMECONSOLE )
|
|
|
|
// ENABLE_COMPETITIVE_CONVAR( convar, range minimum, range maximum, number of additional distinct valid values, distinct valid values... );
|
|
|
|
ENABLE_COMPETITIVE_CONVAR( fps_max, 29, FLT_MAX, 1, 0 ); // force fps_max above 59. One additional value (0) works
|
|
#else
|
|
ENABLE_COMPETITIVE_CONVAR( fps_max, 59, FLT_MAX, 1, 0 ); // force fps_max above 59. One additional value (0) works
|
|
#endif
|
|
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
|
|
ENABLE_COMPETITIVE_CONVAR( cl_updaterate, 10, 150 ); // force cl_updaterate from 10 to 150
|
|
ENABLE_COMPETITIVE_CONVAR( cl_cmdrate, 10, 150 ); // force cl_cmdrate from 10 to 150
|
|
ENABLE_COMPETITIVE_CONVAR( rate, 20480, 786432 ); // force rate above min rate and below max rate
|
|
ENABLE_COMPETITIVE_CONVAR( viewmodel_fov, 54, 68 ); // force viewmodel fov to be between 54 and 68
|
|
ENABLE_COMPETITIVE_CONVAR( viewmodel_offset_x, -2, 2.5 ); // restrict viewmodel positioning
|
|
ENABLE_COMPETITIVE_CONVAR( viewmodel_offset_y, -2, 2 );
|
|
ENABLE_COMPETITIVE_CONVAR( viewmodel_offset_z, -2, 2 );
|
|
ENABLE_COMPETITIVE_CONVAR( cl_bobcycle, 0.98, 0.98 ); // tournament standard
|
|
|
|
// replaced with sv_max_allowed_net_graph convar
|
|
//ENABLE_COMPETITIVE_CONVAR( net_graph, 0, 1 ) // tournament standard
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef GAME_DLL
|
|
CCSPlayer* FindPlayerFromAccountID( uint32 account_id )
|
|
{
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pBasePlayer = UTIL_PlayerByIndex( i );
|
|
if ( !pBasePlayer )
|
|
continue;
|
|
|
|
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
|
|
if ( !pPlayer || pPlayer->IsBot() || !pPlayer->IsConnected() )
|
|
continue;
|
|
|
|
CSteamID steamID;
|
|
pPlayer->GetSteamID( &steamID );
|
|
|
|
if ( steamID.GetAccountID() == account_id )
|
|
return pPlayer;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef GAME_DLL
|
|
|
|
class ClientJob_EMsgGCCStrike15_v2_ServerNotificationForUserPenalty : public GCSDK::CGCClientJob
|
|
{
|
|
public:
|
|
ClientJob_EMsgGCCStrike15_v2_ServerNotificationForUserPenalty( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
|
|
{
|
|
}
|
|
|
|
virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket )
|
|
{
|
|
GCSDK::CProtoBufMsg<CMsgGCCStrike15_v2_ServerNotificationForUserPenalty> msg( pNetPacket );
|
|
DevMsg( "Notification about user penalty: %u/%u (%u sec)\n", msg.Body().account_id(), msg.Body().reason(), msg.Body().seconds() );
|
|
if ( !engine->IsDedicatedServer() || !msg.Body().account_id() )
|
|
return true;
|
|
|
|
if ( !CCSGameRules::sm_mapGcBanInformation.Count() )
|
|
SetDefLessFunc( CCSGameRules::sm_mapGcBanInformation );
|
|
|
|
{
|
|
CCSGameRules::CGcBanInformation_t baninfo = { msg.Body().reason(), Plat_FloatTime() + msg.Body().seconds() };
|
|
CCSGameRules::sm_mapGcBanInformation.InsertOrReplace( msg.Body().account_id(), baninfo );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
GC_REG_CLIENT_JOB( ClientJob_EMsgGCCStrike15_v2_ServerNotificationForUserPenalty, k_EMsgGCCStrike15_v2_ServerNotificationForUserPenalty );
|
|
|
|
class ClientJob_EMsgGCCStrike15_v2_MatchEndRewardDropsNotification : public GCSDK::CGCClientJob
|
|
{
|
|
public:
|
|
ClientJob_EMsgGCCStrike15_v2_MatchEndRewardDropsNotification( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
|
|
{
|
|
}
|
|
|
|
virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket )
|
|
{
|
|
GCSDK::CProtoBufMsg<CMsgGCCStrike15_v2_MatchEndRewardDropsNotification> msg( pNetPacket );
|
|
if ( !msg.Body().has_iteminfo() )
|
|
return true;
|
|
|
|
DevMsg( "Notification about user drop: %u %llu (%u-%u-%u)\n", msg.Body().iteminfo().accountid(), msg.Body().iteminfo().itemid(),
|
|
msg.Body().iteminfo().defindex(), msg.Body().iteminfo().paintindex(), msg.Body().iteminfo().rarity() );
|
|
|
|
if ( msg.Body().iteminfo().accountid() && msg.Body().iteminfo().itemid() && CSGameRules() )
|
|
{
|
|
CSGameRules()->RecordPlayerItemDrop( msg.Body().iteminfo() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
GC_REG_CLIENT_JOB( ClientJob_EMsgGCCStrike15_v2_MatchEndRewardDropsNotification, k_EMsgGCCStrike15_v2_MatchEndRewardDropsNotification );
|
|
|
|
static CMsgGCCStrike15_v2_GiftsLeaderboardResponse g_dataGiftsLeaderboard;
|
|
static double g_dblGiftsLeaderboardReceived = 0;
|
|
void CCSGameRules::CheckForGiftsLeaderboardUpdate()
|
|
{
|
|
}
|
|
|
|
class ClientJob_EMsgGCCStrike15_v2_GiftsLeaderboardResponse : public GCSDK::CGCClientJob
|
|
{
|
|
public:
|
|
ClientJob_EMsgGCCStrike15_v2_GiftsLeaderboardResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
|
|
{
|
|
}
|
|
|
|
virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket )
|
|
{
|
|
GCSDK::CProtoBufMsg<CMsgGCCStrike15_v2_GiftsLeaderboardResponse> msg( pNetPacket );
|
|
if ( !msg.Body().has_servertime() )
|
|
return true;
|
|
|
|
// Set our cached structure
|
|
g_dataGiftsLeaderboard = msg.Body();
|
|
g_dblGiftsLeaderboardReceived = Plat_FloatTime();
|
|
|
|
if ( CCSGameRules *pCSGR = CSGameRules() )
|
|
{ // Copy gifts
|
|
pCSGR->m_numGlobalGiftsGiven = g_dataGiftsLeaderboard.total_gifts_given();
|
|
pCSGR->m_numGlobalGifters = g_dataGiftsLeaderboard.total_givers();
|
|
pCSGR->m_numGlobalGiftsPeriodSeconds = g_dataGiftsLeaderboard.time_period_seconds();
|
|
|
|
for ( int j = 0; j < MAX_GIFT_GIVERS_FEATURED_COUNT; ++ j )
|
|
{
|
|
pCSGR->m_arrFeaturedGiftersAccounts.Set( j, ( j < g_dataGiftsLeaderboard.entries().size() ) ? g_dataGiftsLeaderboard.entries( j ).accountid() : 0 );
|
|
pCSGR->m_arrFeaturedGiftersGifts.Set( j, ( j < g_dataGiftsLeaderboard.entries().size() ) ? g_dataGiftsLeaderboard.entries( j ).gifts() : 0 );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
GC_REG_CLIENT_JOB( ClientJob_EMsgGCCStrike15_v2_GiftsLeaderboardResponse, k_EMsgGCCStrike15_v2_GiftsLeaderboardResponse );
|
|
|
|
|
|
|
|
#endif // GAME_DLL
|
|
|
|
#ifndef CLIENT_DLL
|
|
bool CCSGameRules::OnReplayPrompt( CBasePlayer *pVictim, CBasePlayer *pScorer )
|
|
{
|
|
if ( m_iRoundWinStatus != WINNER_NONE )
|
|
{
|
|
// victim killed after the end of round: do not replay
|
|
return false;
|
|
}
|
|
|
|
return CTeamplayRules::OnReplayPrompt( pVictim, pScorer ); // delegate to the base class
|
|
}
|
|
#endif
|
|
|
|
|