//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============ // // Purpose: The TF Game rules // // $NoKeywords: $ //============================================================================= #include "cbase.h" #include "tf_gamerules.h" #include "ammodef.h" #include "KeyValues.h" #include "tf_weaponbase.h" #include "time.h" #ifdef CLIENT_DLL #include #include "c_tf_player.h" #include "c_tf_objective_resource.h" #else #include "basemultiplayerplayer.h" #include "voice_gamemgr.h" #include "items.h" #include "team.h" #include "tf_bot_temp.h" #include "tf_player.h" #include "tf_team.h" #include "player_resource.h" #include "entity_tfstart.h" #include "filesystem.h" #include "tf_obj.h" #include "tf_objective_resource.h" #include "tf_player_resource.h" #include "team_control_point_master.h" #include "playerclass_info_parse.h" #include "team_control_point_master.h" #include "coordsize.h" #include "entity_healthkit.h" #include "tf_gamestats.h" #include "entity_capture_flag.h" #include "tf_player_resource.h" #include "tf_obj_sentrygun.h" #include "tier0/icommandline.h" #include "activitylist.h" #include "AI_ResponseSystem.h" #include "hl2orange.spa.h" #include "hltvdirector.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define ITEM_RESPAWN_TIME 10.0f enum { BIRTHDAY_RECALCULATE, BIRTHDAY_OFF, BIRTHDAY_ON, }; static int g_TauntCamAchievements[] = { 0, // TF_CLASS_UNDEFINED 0, // TF_CLASS_SCOUT, 0, // TF_CLASS_SNIPER, 0, // TF_CLASS_SOLDIER, 0, // TF_CLASS_DEMOMAN, 0, // TF_CLASS_MEDIC, 0, // TF_CLASS_HEAVYWEAPONS, 0, // TF_CLASS_PYRO, 0, // TF_CLASS_SPY, 0, // TF_CLASS_ENGINEER, 0, // TF_CLASS_CIVILIAN, 0, // TF_CLASS_COUNT_ALL, }; extern ConVar mp_capstyle; extern ConVar sv_turbophysics; ConVar tf_caplinear( "tf_caplinear", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "If set to 1, teams must capture control points linearly." ); ConVar tf_stalematechangeclasstime( "tf_stalematechangeclasstime", "20", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time that players are allowed to change class in stalemates." ); ConVar tf_birthday( "tf_birthday", "0", FCVAR_NOTIFY | FCVAR_REPLICATED ); #ifdef GAME_DLL // TF overrides the default value of this convar ConVar mp_waitingforplayers_time( "mp_waitingforplayers_time", (IsX360()?"15":"30"), FCVAR_GAMEDLL | FCVAR_DEVELOPMENTONLY, "WaitingForPlayers time length in seconds" ); ConVar tf_gravetalk( "tf_gravetalk", "1", FCVAR_NOTIFY, "Allows living players to hear dead players using text/voice chat." ); ConVar tf_spectalk( "tf_spectalk", "1", FCVAR_NOTIFY, "Allows living players to hear spectators using text chat." ); #endif #ifdef GAME_DLL void ValidateCapturesPerRound( IConVar *pConVar, const char *oldValue, float flOldValue ) { ConVarRef var( pConVar ); if ( var.GetInt() <= 0 ) { // reset the flag captures being played in the current round int nTeamCount = TFTeamMgr()->GetTeamCount(); for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam ) { CTFTeam *pTeam = GetGlobalTFTeam( iTeam ); if ( !pTeam ) continue; pTeam->SetFlagCaptures( 0 ); } } } #endif ConVar tf_flag_caps_per_round( "tf_flag_caps_per_round", "3", FCVAR_REPLICATED, "Number of flag captures per round on CTF maps. Set to 0 to disable.", true, 0, true, 9 #ifdef GAME_DLL , ValidateCapturesPerRound #endif ); /** * Player hull & eye position for standing, ducking, etc. This version has a taller * player height, but goldsrc-compatible collision bounds. */ static CViewVectors g_TFViewVectors( Vector( 0, 0, 72 ), //VEC_VIEW (m_vView) eye position Vector(-24, -24, 0 ), //VEC_HULL_MIN (m_vHullMin) hull min Vector( 24, 24, 82 ), //VEC_HULL_MAX (m_vHullMax) hull max Vector(-24, -24, 0 ), //VEC_DUCK_HULL_MIN (m_vDuckHullMin) duck hull min Vector( 24, 24, 55 ), //VEC_DUCK_HULL_MAX (m_vDuckHullMax) duck hull max Vector( 0, 0, 45 ), //VEC_DUCK_VIEW (m_vDuckView) duck view Vector( -10, -10, -10 ), //VEC_OBS_HULL_MIN (m_vObsHullMin) observer hull min Vector( 10, 10, 10 ), //VEC_OBS_HULL_MAX (m_vObsHullMax) observer hull max Vector( 0, 0, 14 ) //VEC_DEAD_VIEWHEIGHT (m_vDeadViewHeight) dead view height ); Vector g_TFClassViewVectors[10] = { Vector( 0, 0, 72 ), // TF_CLASS_UNDEFINED Vector( 0, 0, 65 ), // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS Vector( 0, 0, 75 ), // TF_CLASS_SNIPER, Vector( 0, 0, 68 ), // TF_CLASS_SOLDIER, Vector( 0, 0, 68 ), // TF_CLASS_DEMOMAN, Vector( 0, 0, 75 ), // TF_CLASS_MEDIC, Vector( 0, 0, 75 ), // TF_CLASS_HEAVYWEAPONS, Vector( 0, 0, 68 ), // TF_CLASS_PYRO, Vector( 0, 0, 75 ), // TF_CLASS_SPY, Vector( 0, 0, 68 ), // TF_CLASS_ENGINEER, // TF_LAST_NORMAL_CLASS }; const CViewVectors *CTFGameRules::GetViewVectors() const { return &g_TFViewVectors; } REGISTER_GAMERULES_CLASS( CTFGameRules ); BEGIN_NETWORK_TABLE_NOBASE( CTFGameRules, DT_TFGameRules ) #ifdef CLIENT_DLL RecvPropInt( RECVINFO( m_nGameType ) ), RecvPropString( RECVINFO( m_pszTeamGoalStringRed ) ), RecvPropString( RECVINFO( m_pszTeamGoalStringBlue ) ), #else SendPropInt( SENDINFO( m_nGameType ), 3, SPROP_UNSIGNED ), SendPropString( SENDINFO( m_pszTeamGoalStringRed ) ), SendPropString( SENDINFO( m_pszTeamGoalStringBlue ) ), #endif END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_gamerules, CTFGameRulesProxy ); IMPLEMENT_NETWORKCLASS_ALIASED( TFGameRulesProxy, DT_TFGameRulesProxy ) #ifdef CLIENT_DLL void RecvProxy_TFGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) { CTFGameRules *pRules = TFGameRules(); Assert( pRules ); *pOut = pRules; } BEGIN_RECV_TABLE( CTFGameRulesProxy, DT_TFGameRulesProxy ) RecvPropDataTable( "tf_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_TFGameRules ), RecvProxy_TFGameRules ) END_RECV_TABLE() #else void *SendProxy_TFGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) { CTFGameRules *pRules = TFGameRules(); Assert( pRules ); pRecipients->SetAllRecipients(); return pRules; } BEGIN_SEND_TABLE( CTFGameRulesProxy, DT_TFGameRulesProxy ) SendPropDataTable( "tf_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_TFGameRules ), SendProxy_TFGameRules ) END_SEND_TABLE() #endif #ifdef GAME_DLL BEGIN_DATADESC( CTFGameRulesProxy ) // Inputs. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetRedTeamRespawnWaveTime", InputSetRedTeamRespawnWaveTime ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBlueTeamRespawnWaveTime", InputSetBlueTeamRespawnWaveTime ), DEFINE_INPUTFUNC( FIELD_FLOAT, "AddRedTeamRespawnWaveTime", InputAddRedTeamRespawnWaveTime ), DEFINE_INPUTFUNC( FIELD_FLOAT, "AddBlueTeamRespawnWaveTime", InputAddBlueTeamRespawnWaveTime ), DEFINE_INPUTFUNC( FIELD_STRING, "SetRedTeamGoalString", InputSetRedTeamGoalString ), DEFINE_INPUTFUNC( FIELD_STRING, "SetBlueTeamGoalString", InputSetBlueTeamGoalString ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRedTeamRole", InputSetRedTeamRole ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetBlueTeamRole", InputSetBlueTeamRole ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputSetRedTeamRespawnWaveTime( inputdata_t &inputdata ) { TFGameRules()->SetTeamRespawnWaveTime( TF_TEAM_RED, inputdata.value.Float() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputSetBlueTeamRespawnWaveTime( inputdata_t &inputdata ) { TFGameRules()->SetTeamRespawnWaveTime( TF_TEAM_BLUE, inputdata.value.Float() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputAddRedTeamRespawnWaveTime( inputdata_t &inputdata ) { TFGameRules()->AddTeamRespawnWaveTime( TF_TEAM_RED, inputdata.value.Float() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputAddBlueTeamRespawnWaveTime( inputdata_t &inputdata ) { TFGameRules()->AddTeamRespawnWaveTime( TF_TEAM_BLUE, inputdata.value.Float() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputSetRedTeamGoalString( inputdata_t &inputdata ) { TFGameRules()->SetTeamGoalString( TF_TEAM_RED, inputdata.value.String() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputSetBlueTeamGoalString( inputdata_t &inputdata ) { TFGameRules()->SetTeamGoalString( TF_TEAM_BLUE, inputdata.value.String() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputSetRedTeamRole( inputdata_t &inputdata ) { CTFTeam *pTeam = TFTeamMgr()->GetTeam( TF_TEAM_RED ); if ( pTeam ) { pTeam->SetRole( inputdata.value.Int() ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::InputSetBlueTeamRole( inputdata_t &inputdata ) { CTFTeam *pTeam = TFTeamMgr()->GetTeam( TF_TEAM_BLUE ); if ( pTeam ) { pTeam->SetRole( inputdata.value.Int() ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRulesProxy::Activate() { TFGameRules()->Activate(); BaseClass::Activate(); } #endif // (We clamp ammo ourselves elsewhere). ConVar ammo_max( "ammo_max", "5000", FCVAR_REPLICATED ); #ifndef CLIENT_DLL ConVar sk_plr_dmg_grenade( "sk_plr_dmg_grenade","0"); // Very lame that the base code needs this defined #endif //----------------------------------------------------------------------------- // Purpose: // Input : iDmgType - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFGameRules::Damage_IsTimeBased( int iDmgType ) { // Damage types that are time-based. return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_DROWNRECOVER ) ) != 0 ); } //----------------------------------------------------------------------------- // Purpose: // Input : iDmgType - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFGameRules::Damage_ShowOnHUD( int iDmgType ) { // Damage types that have client HUD art. return ( ( iDmgType & ( DMG_DROWN | DMG_BURN | DMG_NERVEGAS | DMG_SHOCK ) ) != 0 ); } //----------------------------------------------------------------------------- // Purpose: // Input : iDmgType - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFGameRules::Damage_ShouldNotBleed( int iDmgType ) { // Should always bleed currently. return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFGameRules::Damage_GetTimeBased( void ) { int iDamage = ( DMG_PARALYZE | DMG_NERVEGAS | DMG_DROWNRECOVER ); return iDamage; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFGameRules::Damage_GetShowOnHud( void ) { int iDamage = ( DMG_DROWN | DMG_BURN | DMG_NERVEGAS | DMG_SHOCK ); return iDamage; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFGameRules::Damage_GetShouldNotBleed( void ) { return 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFGameRules::CTFGameRules() { #ifdef GAME_DLL // Create teams. TFTeamMgr()->Init(); ResetMapTime(); // Create the team managers // for ( int i = 0; i < ARRAYSIZE( teamnames ); i++ ) // { // CTeam *pTeam = static_cast(CreateEntityByName( "tf_team" )); // pTeam->Init( sTeamNames[i], i ); // // g_Teams.AddToTail( pTeam ); // } m_flIntermissionEndTime = 0.0f; m_flNextPeriodicThink = 0.0f; ListenForGameEvent( "teamplay_point_captured" ); ListenForGameEvent( "teamplay_capture_blocked" ); ListenForGameEvent( "teamplay_round_win" ); ListenForGameEvent( "teamplay_flag_event" ); Q_memset( m_vecPlayerPositions,0, sizeof(m_vecPlayerPositions) ); m_iPrevRoundState = -1; m_iCurrentRoundState = -1; m_iCurrentMiniRoundMask = 0; m_flTimerMayExpireAt = -1.0f; // Lets execute a map specific cfg file // ** execute this after server.cfg! char szCommand[32]; Q_snprintf( szCommand, sizeof( szCommand ), "exec %s.cfg\n", STRING( gpGlobals->mapname ) ); engine->ServerCommand( szCommand ); #else // GAME_DLL ListenForGameEvent( "game_newmap" ); #endif // Initialize the game type m_nGameType.Set( TF_GAMETYPE_UNDEFINED ); // Initialize the classes here. InitPlayerClasses(); // Set turbo physics on. Do it here for now. sv_turbophysics.SetValue( 1 ); // Initialize the team manager here, etc... // If you hit these asserts its because you added or removed a weapon type // and didn't also add or remove the weapon name or damage type from the // arrays defined in tf_shareddefs.cpp Assert( g_aWeaponDamageTypes[TF_WEAPON_COUNT] == TF_DMG_SENTINEL_VALUE ); Assert( FStrEq( g_aWeaponNames[TF_WEAPON_COUNT], "TF_WEAPON_COUNT" ) ); m_iPreviousRoundWinners = TEAM_UNASSIGNED; m_iBirthdayMode = BIRTHDAY_RECALCULATE; m_pszTeamGoalStringRed.GetForModify()[0] = '\0'; m_pszTeamGoalStringBlue.GetForModify()[0] = '\0'; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::FlagsMayBeCapped( void ) { if ( State_Get() != GR_STATE_TEAM_WIN ) return true; return false; } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: Determines whether we should allow mp_timelimit to trigger a map change //----------------------------------------------------------------------------- bool CTFGameRules::CanChangelevelBecauseOfTimeLimit( void ) { CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; // we only want to deny a map change triggered by mp_timelimit if we're not forcing a map reset, // we're playing mini-rounds, and the master says we need to play all of them before changing (for maps like Dustbowl) if ( !m_bForceMapReset && pMaster && pMaster->PlayingMiniRounds() && pMaster->ShouldPlayAllControlPointRounds() ) { if ( pMaster->FindControlPointRoundToPlay() ) { return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::CanGoToStalemate( void ) { // In CTF, don't go to stalemate if one of the flags isn't at home if ( m_nGameType == TF_GAMETYPE_CTF ) { CCaptureFlag *pFlag = dynamic_cast ( gEntList.FindEntityByClassname( NULL, "item_teamflag" ) ); while( pFlag ) { if ( pFlag->IsDropped() || pFlag->IsStolen() ) return false; pFlag = dynamic_cast ( gEntList.FindEntityByClassname( pFlag, "item_teamflag" ) ); } // check that one team hasn't won by capping if ( CheckCapsPerRound() ) return false; } return BaseClass::CanGoToStalemate(); } // Classnames of entities that are preserved across round restarts static const char *s_PreserveEnts[] = { "tf_gamerules", "tf_team_manager", "tf_player_manager", "tf_team", "tf_objective_resource", "keyframe_rope", "move_rope", "tf_viewmodel", "", // END Marker }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::Activate() { m_iBirthdayMode = BIRTHDAY_RECALCULATE; m_nGameType.Set( TF_GAMETYPE_UNDEFINED ); CCaptureFlag *pFlag = dynamic_cast ( gEntList.FindEntityByClassname( NULL, "item_teamflag" ) ); if ( pFlag ) { m_nGameType.Set( TF_GAMETYPE_CTF ); } if ( g_hControlPointMasters.Count() ) { m_nGameType.Set( TF_GAMETYPE_CP ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::AllowDamage( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { bool bRetVal = true; if ( ( State_Get() == GR_STATE_TEAM_WIN ) && pVictim ) { if ( pVictim->GetTeamNumber() == GetWinningTeam() ) { CBaseTrigger *pTrigger = dynamic_cast< CBaseTrigger *>( info.GetInflictor() ); // we don't want players on the winning team to be // hurt by team-specific trigger_hurt entities during the bonus time if ( pTrigger && pTrigger->UsesFilter() ) { bRetVal = false; } } } return bRetVal; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::SetTeamGoalString( int iTeam, const char *pszGoal ) { if ( iTeam == TF_TEAM_RED ) { if ( !pszGoal || !pszGoal[0] ) { m_pszTeamGoalStringRed.GetForModify()[0] = '\0'; } else { if ( Q_stricmp( m_pszTeamGoalStringRed.Get(), pszGoal ) ) { Q_strncpy( m_pszTeamGoalStringRed.GetForModify(), pszGoal, MAX_TEAMGOAL_STRING ); } } } else if ( iTeam == TF_TEAM_BLUE ) { if ( !pszGoal || !pszGoal[0] ) { m_pszTeamGoalStringBlue.GetForModify()[0] = '\0'; } else { if ( Q_stricmp( m_pszTeamGoalStringBlue.Get(), pszGoal ) ) { Q_strncpy( m_pszTeamGoalStringBlue.GetForModify(), pszGoal, MAX_TEAMGOAL_STRING ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::RoundCleanupShouldIgnore( CBaseEntity *pEnt ) { if ( FindInList( s_PreserveEnts, pEnt->GetClassname() ) ) return true; //There has got to be a better way of doing this. if ( Q_strstr( pEnt->GetClassname(), "tf_weapon_" ) ) return true; return BaseClass::RoundCleanupShouldIgnore( pEnt ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::ShouldCreateEntity( const char *pszClassName ) { if ( FindInList( s_PreserveEnts, pszClassName ) ) return false; return BaseClass::ShouldCreateEntity( pszClassName ); } void CTFGameRules::CleanUpMap( void ) { BaseClass::CleanUpMap(); if ( HLTVDirector() ) { HLTVDirector()->BuildCameraList(); } } //----------------------------------------------------------------------------- // Purpose: //-----------------------------------------------------------------------------ob void CTFGameRules::RecalculateControlPointState( void ) { Assert( ObjectiveResource() ); if ( !g_hControlPointMasters.Count() ) return; if ( g_pObjectiveResource && g_pObjectiveResource->PlayingMiniRounds() ) return; for ( int iTeam = LAST_SHARED_TEAM+1; iTeam < GetNumberOfTeams(); iTeam++ ) { int iFarthestPoint = GetFarthestOwnedControlPoint( iTeam, true ); if ( iFarthestPoint == -1 ) continue; // Now enable all spawn points for that spawn point CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_teamspawn" ); while( pSpot ) { CTFTeamSpawn *pTFSpawn = assert_cast(pSpot); if ( pTFSpawn->GetControlPoint() ) { if ( pTFSpawn->GetTeamNumber() == iTeam ) { if ( pTFSpawn->GetControlPoint()->GetPointIndex() == iFarthestPoint ) { pTFSpawn->SetDisabled( false ); } else { pTFSpawn->SetDisabled( true ); } } } pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_teamspawn" ); } } } //----------------------------------------------------------------------------- // Purpose: Called when a new round is being initialized //----------------------------------------------------------------------------- void CTFGameRules::SetupOnRoundStart( void ) { for ( int i = 0; i < MAX_TEAMS; i++ ) { ObjectiveResource()->SetBaseCP( -1, i ); } for ( int i = 0; i < TF_TEAM_COUNT; i++ ) { m_iNumCaps[i] = 0; } // Let all entities know that a new round is starting CBaseEntity *pEnt = gEntList.FirstEnt(); while( pEnt ) { variant_t emptyVariant; pEnt->AcceptInput( "RoundSpawn", NULL, NULL, emptyVariant, 0 ); pEnt = gEntList.NextEnt( pEnt ); } // All entities have been spawned, now activate them pEnt = gEntList.FirstEnt(); while( pEnt ) { variant_t emptyVariant; pEnt->AcceptInput( "RoundActivate", NULL, NULL, emptyVariant, 0 ); pEnt = gEntList.NextEnt( pEnt ); } if ( g_pObjectiveResource && !g_pObjectiveResource->PlayingMiniRounds() ) { // Find all the control points with associated spawnpoints memset( m_bControlSpawnsPerTeam, 0, sizeof(bool) * MAX_TEAMS * MAX_CONTROL_POINTS ); CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_teamspawn" ); while( pSpot ) { CTFTeamSpawn *pTFSpawn = assert_cast(pSpot); if ( pTFSpawn->GetControlPoint() ) { m_bControlSpawnsPerTeam[ pTFSpawn->GetTeamNumber() ][ pTFSpawn->GetControlPoint()->GetPointIndex() ] = true; pTFSpawn->SetDisabled( true ); } pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_teamspawn" ); } RecalculateControlPointState(); SetRoundOverlayDetails(); } #ifdef GAME_DLL m_szMostRecentCappers[0] = 0; #endif } //----------------------------------------------------------------------------- // Purpose: Called when a new round is off and running //----------------------------------------------------------------------------- void CTFGameRules::SetupOnRoundRunning( void ) { // Let out control point masters know that the round has started for ( int i = 0; i < g_hControlPointMasters.Count(); i++ ) { variant_t emptyVariant; if ( g_hControlPointMasters[i] ) { g_hControlPointMasters[i]->AcceptInput( "RoundStart", NULL, NULL, emptyVariant, 0 ); } } // Reset player speeds after preround lock CTFPlayer *pPlayer; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( !pPlayer ) continue; pPlayer->TeamFortress_SetSpeed(); pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_ROUND_START ); } } //----------------------------------------------------------------------------- // Purpose: Called before a new round is started (so the previous round can end) //----------------------------------------------------------------------------- void CTFGameRules::PreviousRoundEnd( void ) { // before we enter a new round, fire the "end output" for the previous round if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] ) { g_hControlPointMasters[0]->FireRoundEndOutput(); } m_iPreviousRoundWinners = GetWinningTeam(); } //----------------------------------------------------------------------------- // Purpose: Called when a round has entered stalemate mode (timer has run out) //----------------------------------------------------------------------------- void CTFGameRules::SetupOnStalemateStart( void ) { // Remove everyone's objects for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { pPlayer->TeamFortress_RemoveEverythingFromWorld(); } } // Respawn all the players RespawnPlayers( true ); // Disable all the active health packs in the world m_hDisabledHealthKits.Purge(); CHealthKit *pHealthPack = gEntList.NextEntByClass( (CHealthKit *)NULL ); while ( pHealthPack ) { if ( !pHealthPack->IsDisabled() ) { pHealthPack->SetDisabled( true ); m_hDisabledHealthKits.AddToTail( pHealthPack ); } pHealthPack = gEntList.NextEntByClass( pHealthPack ); } CTFPlayer *pPlayer; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( !pPlayer ) continue; pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_SUDDENDEATH_START ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::SetupOnStalemateEnd( void ) { // Reenable all the health packs we disabled for ( int i = 0; i < m_hDisabledHealthKits.Count(); i++ ) { if ( m_hDisabledHealthKits[i] ) { m_hDisabledHealthKits[i]->SetDisabled( false ); } } m_hDisabledHealthKits.Purge(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::InitTeams( void ) { BaseClass::InitTeams(); // clear the player class data ResetFilePlayerClassInfoDatabase(); } ConVar tf_fixedup_damage_radius ( "tf_fixedup_damage_radius", "1", FCVAR_DEVELOPMENTONLY ); //----------------------------------------------------------------------------- // Purpose: // Input : &info - // &vecSrcIn - // flRadius - // iClassIgnore - // *pEntityIgnore - //----------------------------------------------------------------------------- void CTFGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore ) { const int MASK_RADIUS_DAMAGE = MASK_SHOT&(~CONTENTS_HITBOX); CBaseEntity *pEntity = NULL; trace_t tr; float falloff; Vector vecSpot; Vector vecSrc = vecSrcIn; if ( info.GetDamageType() & DMG_RADIUS_MAX ) falloff = 0.0; else if ( info.GetDamageType() & DMG_HALF_FALLOFF ) falloff = 0.5; else if ( flRadius ) falloff = info.GetDamage() / flRadius; else falloff = 1.0; CBaseEntity *pInflictor = info.GetInflictor(); // float flHalfRadiusSqr = Square( flRadius / 2.0f ); // iterate on all entities in the vicinity. for ( CEntitySphereQuery sphere( vecSrc, flRadius ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) { // This value is used to scale damage when the explosion is blocked by some other object. float flBlockedDamagePercent = 0.0f; if ( pEntity == pEntityIgnore ) continue; if ( pEntity->m_takedamage == DAMAGE_NO ) continue; // 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; } // Check that the explosion can 'see' this entity. vecSpot = pEntity->BodyTarget( vecSrc, false ); UTIL_TraceLine( vecSrc, vecSpot, MASK_RADIUS_DAMAGE, info.GetInflictor(), COLLISION_GROUP_PROJECTILE, &tr ); if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity ) continue; // Adjust the damage - apply falloff. float flAdjustedDamage = 0.0f; float flDistanceToEntity; // Rockets store the ent they hit as the enemy and have already // dealt full damage to them by this time if ( pInflictor && ( pEntity == pInflictor->GetEnemy() ) ) { // Full damage, we hit this entity directly flDistanceToEntity = 0; } else if ( pEntity->IsPlayer() ) { // Use whichever is closer, absorigin or worldspacecenter float flToWorldSpaceCenter = ( vecSrc - pEntity->WorldSpaceCenter() ).Length(); float flToOrigin = ( vecSrc - pEntity->GetAbsOrigin() ).Length(); flDistanceToEntity = MIN( flToWorldSpaceCenter, flToOrigin ); } else { flDistanceToEntity = ( vecSrc - tr.endpos ).Length(); } if ( tf_fixedup_damage_radius.GetBool() ) { flAdjustedDamage = RemapValClamped( flDistanceToEntity, 0, flRadius, info.GetDamage(), info.GetDamage() * falloff ); } else { flAdjustedDamage = flDistanceToEntity * falloff; flAdjustedDamage = info.GetDamage() - flAdjustedDamage; } // Take a little less damage from yourself if ( tr.m_pEnt == info.GetAttacker()) { flAdjustedDamage = flAdjustedDamage * 0.75; } if ( flAdjustedDamage <= 0 ) continue; // the explosion can 'see' this entity, so hurt them! if (tr.startsolid) { // if we're stuck inside them, fixup the position and distance tr.endpos = vecSrc; tr.fraction = 0.0; } CTakeDamageInfo adjustedInfo = info; //Msg("%s: Blocked damage: %f percent (in:%f out:%f)\n", pEntity->GetClassname(), flBlockedDamagePercent * 100, flAdjustedDamage, flAdjustedDamage - (flAdjustedDamage * flBlockedDamagePercent) ); adjustedInfo.SetDamage( flAdjustedDamage - (flAdjustedDamage * flBlockedDamagePercent) ); // Now make a consideration for skill level! if( info.GetAttacker() && info.GetAttacker()->IsPlayer() && pEntity->IsNPC() ) { // An explosion set off by the player is harming an NPC. Adjust damage accordingly. adjustedInfo.AdjustPlayerDamageInflictedForSkillLevel(); } Vector dir = vecSpot - vecSrc; 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 ); } 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 ); } if ( tr.fraction != 1.0 && pEntity == tr.m_pEnt ) { 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, tr.endpos, dir ); } } // --------------------------------------------------------------------------------------------------- // // Voice helper // --------------------------------------------------------------------------------------------------- // class CVoiceGameMgrHelper : public IVoiceGameMgrHelper { public: virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity ) { // Dead players can only be heard by other dead team mates but only if a match is in progress if ( TFGameRules()->State_Get() != GR_STATE_TEAM_WIN && TFGameRules()->State_Get() != GR_STATE_GAME_OVER ) { if ( pTalker->IsAlive() == false ) { if ( pListener->IsAlive() == false || tf_gravetalk.GetBool() ) return ( pListener->InSameTeam( pTalker ) ); return false; } } return ( pListener->InSameTeam( pTalker ) ); } }; CVoiceGameMgrHelper g_VoiceGameMgrHelper; IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; // Load the objects.txt file. class CObjectsFileLoad : public CAutoGameSystem { public: virtual bool Init() { LoadObjectInfos( filesystem ); return true; } } g_ObjectsFileLoad; // --------------------------------------------------------------------------------------------------- // // Globals. // --------------------------------------------------------------------------------------------------- // /* // NOTE: the indices here must match TEAM_UNASSIGNED, TEAM_SPECTATOR, TF_TEAM_RED, TF_TEAM_BLUE, etc. char *sTeamNames[] = { "Unassigned", "Spectator", "Red", "Blue" }; */ // --------------------------------------------------------------------------------------------------- // // Global helper functions. // --------------------------------------------------------------------------------------------------- // // World.cpp calls this but we don't use it in TF. void InitBodyQue() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFGameRules::~CTFGameRules() { // Note, don't delete each team since they are in the gEntList and will // automatically be deleted from there, instead. TFTeamMgr()->Shutdown(); ShutdownCustomResponseRulesDicts(); } //----------------------------------------------------------------------------- // Purpose: TF2 Specific Client Commands // Input : // Output : //----------------------------------------------------------------------------- bool CTFGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) { CTFPlayer *pPlayer = ToTFPlayer( pEdict ); const char *pcmd = args[0]; if ( FStrEq( pcmd, "objcmd" ) ) { if ( args.ArgC() < 3 ) return true; int entindex = atoi( args[1] ); edict_t* pEdict = INDEXENT(entindex); if ( pEdict ) { CBaseEntity* pBaseEntity = GetContainingEntity(pEdict); CBaseObject* pObject = dynamic_cast(pBaseEntity); if ( pObject ) { // We have to be relatively close to the object too... // BUG! Some commands need to get sent without the player being near the object, // eg delayed dismantle commands. Come up with a better way to ensure players aren't // entering these commands in the console. //float flDistSq = pObject->GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ); //if (flDistSq <= (MAX_OBJECT_SCREEN_INPUT_DISTANCE * MAX_OBJECT_SCREEN_INPUT_DISTANCE)) { // Strip off the 1st two arguments and make a new argument string CCommand objectArgs( args.ArgC() - 2, &args.ArgV()[2]); pObject->ClientCommand( pPlayer, objectArgs ); } } } return true; } // Handle some player commands here as they relate more directly to gamerules state if ( FStrEq( pcmd, "nextmap" ) ) { if ( pPlayer->m_flNextTimeCheck < gpGlobals->curtime ) { char szNextMap[32]; if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) ) { Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) ); } else { GetNextLevelName( szNextMap, sizeof( szNextMap ) ); } ClientPrint( pPlayer, HUD_PRINTTALK, "#TF_nextmap", szNextMap); pPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1; } return true; } else if ( FStrEq( pcmd, "timeleft" ) ) { if ( pPlayer->m_flNextTimeCheck < gpGlobals->curtime ) { if ( mp_timelimit.GetInt() > 0 ) { int iTimeLeft = GetTimeLeft(); char szMinutes[5]; char szSeconds[3]; if ( iTimeLeft <= 0 ) { Q_snprintf( szMinutes, sizeof(szMinutes), "0" ); Q_snprintf( szSeconds, sizeof(szSeconds), "00" ); } else { Q_snprintf( szMinutes, sizeof(szMinutes), "%d", iTimeLeft / 60 ); Q_snprintf( szSeconds, sizeof(szSeconds), "%02d", iTimeLeft % 60 ); } ClientPrint( pPlayer, HUD_PRINTTALK, "#TF_timeleft", szMinutes, szSeconds ); } else { ClientPrint( pPlayer, HUD_PRINTTALK, "#TF_timeleft_nolimit" ); } pPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1; } return true; } else if( pPlayer->ClientCommand( args ) ) { return true; } return BaseClass::ClientCommand( pEdict, args ); } // Add the ability to ignore the world trace void CTFGameRules::Think() { if ( !g_fGameOver ) { if ( gpGlobals->curtime > m_flNextPeriodicThink ) { if ( State_Get() != GR_STATE_TEAM_WIN ) { if ( CheckCapsPerRound() ) return; } } } BaseClass::Think(); } //Runs think for all player's conditions //Need to do this here instead of the player so players that crash still run their important thinks void CTFGameRules::RunPlayerConditionThink ( void ) { for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { pPlayer->m_Shared.ConditionGameRulesThink(); } } } void CTFGameRules::FrameUpdatePostEntityThink() { BaseClass::FrameUpdatePostEntityThink(); RunPlayerConditionThink(); } bool CTFGameRules::CheckCapsPerRound() { if ( tf_flag_caps_per_round.GetInt() > 0 ) { int iMaxCaps = -1; CTFTeam *pMaxTeam = NULL; // check to see if any team has won a "round" int nTeamCount = TFTeamMgr()->GetTeamCount(); for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam ) { CTFTeam *pTeam = GetGlobalTFTeam( iTeam ); if ( !pTeam ) continue; // we might have more than one team over the caps limit (if the server op lowered the limit) // so loop through to see who has the most among teams over the limit if ( pTeam->GetFlagCaptures() >= tf_flag_caps_per_round.GetInt() ) { if ( pTeam->GetFlagCaptures() > iMaxCaps ) { iMaxCaps = pTeam->GetFlagCaptures(); pMaxTeam = pTeam; } } } if ( iMaxCaps != -1 && pMaxTeam != NULL ) { SetWinningTeam( pMaxTeam->GetTeamNumber(), WINREASON_FLAG_CAPTURE_LIMIT ); return true; } } return false; } bool CTFGameRules::CheckWinLimit() { if ( mp_winlimit.GetInt() != 0 ) { bool bWinner = false; if ( TFTeamMgr()->GetTeam( TF_TEAM_BLUE )->GetScore() >= mp_winlimit.GetInt() ) { UTIL_LogPrintf( "Team \"BLUE\" triggered \"Intermission_Win_Limit\"\n" ); bWinner = true; } else if ( TFTeamMgr()->GetTeam( TF_TEAM_RED )->GetScore() >= mp_winlimit.GetInt() ) { UTIL_LogPrintf( "Team \"RED\" triggered \"Intermission_Win_Limit\"\n" ); bWinner = true; } if ( bWinner ) { IGameEvent *event = gameeventmanager->CreateEvent( "tf_game_over" ); if ( event ) { event->SetString( "reason", "Reached Win Limit" ); gameeventmanager->FireEvent( event ); } GoToIntermission(); return true; } } return false; } bool CTFGameRules::IsInPreMatch() const { // TFTODO return (cb_prematch_time > gpGlobals->time) return false; } float CTFGameRules::GetPreMatchEndTime() const { //TFTODO: implement this. return gpGlobals->curtime; } void CTFGameRules::GoToIntermission( void ) { CTF_GameStats.Event_GameEnd(); BaseClass::GoToIntermission(); } bool CTFGameRules::FPlayerCanTakeDamage(CBasePlayer *pPlayer, CBaseEntity *pAttacker, const CTakeDamageInfo &info) { // guard against NULL pointers if players disconnect if ( !pPlayer || !pAttacker ) return false; // if pAttacker is an object, we can only do damage if pPlayer is our builder if ( pAttacker->IsBaseObject() ) { CBaseObject *pObj = ( CBaseObject *)pAttacker; if ( pObj->GetBuilder() == pPlayer || pPlayer->GetTeamNumber() != pObj->GetTeamNumber() ) { // Builder and enemies return true; } else { // Teammates of the builder return false; } } return BaseClass::FPlayerCanTakeDamage(pPlayer, pAttacker, info); } Vector DropToGround( CBaseEntity *pMainEnt, const Vector &vPos, const Vector &vMins, const Vector &vMaxs ) { trace_t trace; UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace ); return trace.endpos; } void TestSpawnPointType( const char *pEntClassName ) { // Find the next spawn spot. CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, pEntClassName ); while( pSpot ) { // trace a box here Vector vTestMins = pSpot->GetAbsOrigin() + VEC_HULL_MIN; Vector vTestMaxs = pSpot->GetAbsOrigin() + VEC_HULL_MAX; if ( UTIL_IsSpaceEmpty( pSpot, vTestMins, vTestMaxs ) ) { // the successful spawn point's location NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 100, 60 ); // drop down to ground Vector GroundPos = DropToGround( NULL, pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX ); // the location the player will spawn at NDebugOverlay::Box( GroundPos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 0, 255, 100, 60 ); // draw the spawn angles QAngle spotAngles = pSpot->GetLocalAngles(); Vector vecForward; AngleVectors( spotAngles, &vecForward ); NDebugOverlay::HorzArrow( pSpot->GetAbsOrigin(), pSpot->GetAbsOrigin() + vecForward * 32, 10, 255, 0, 0, 255, true, 60 ); } else { // failed spawn point location NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 100, 60 ); } // increment pSpot pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); } } // -------------------------------------------------------------------------------- // void TestSpawns() { TestSpawnPointType( "info_player_teamspawn" ); } ConCommand cc_TestSpawns( "map_showspawnpoints", TestSpawns, "Dev - test the spawn points, draws for 60 seconds", FCVAR_CHEAT ); // -------------------------------------------------------------------------------- // void cc_ShowRespawnTimes() { CTFGameRules *pRules = TFGameRules(); CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() ); if ( pRules && pPlayer ) { float flRedMin = ( pRules->m_TeamRespawnWaveTimes[TF_TEAM_RED] >= 0 ? pRules->m_TeamRespawnWaveTimes[TF_TEAM_RED] : mp_respawnwavetime.GetFloat() ); float flRedScalar = pRules->GetRespawnTimeScalar( TF_TEAM_RED ); float flNextRedRespawn = pRules->GetNextRespawnWave( TF_TEAM_RED, NULL ) - gpGlobals->curtime; float flBlueMin = ( pRules->m_TeamRespawnWaveTimes[TF_TEAM_BLUE] >= 0 ? pRules->m_TeamRespawnWaveTimes[TF_TEAM_BLUE] : mp_respawnwavetime.GetFloat() ); float flBlueScalar = pRules->GetRespawnTimeScalar( TF_TEAM_BLUE ); float flNextBlueRespawn = pRules->GetNextRespawnWave( TF_TEAM_BLUE, NULL ) - gpGlobals->curtime; char tempRed[128]; Q_snprintf( tempRed, sizeof( tempRed ), "Red: Min Spawn %2.2f, Scalar %2.2f, Next Spawn In: %.2f\n", flRedMin, flRedScalar, flNextRedRespawn ); char tempBlue[128]; Q_snprintf( tempBlue, sizeof( tempBlue ), "Blue: Min Spawn %2.2f, Scalar %2.2f, Next Spawn In: %.2f\n", flBlueMin, flBlueScalar, flNextBlueRespawn ); ClientPrint( pPlayer, HUD_PRINTTALK, tempRed ); ClientPrint( pPlayer, HUD_PRINTTALK, tempBlue ); } } ConCommand mp_showrespawntimes( "mp_showrespawntimes", cc_ShowRespawnTimes, "Show the min respawn times for the teams" ); // -------------------------------------------------------------------------------- // CBaseEntity *CTFGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) { // get valid spawn point CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint(); // drop down to ground Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX ); // Move the player to the place it said. pPlayer->SetLocalOrigin( GroundPos + Vector(0,0,1) ); pPlayer->SetAbsVelocity( vec3_origin ); pPlayer->SetLocalAngles( pSpawnSpot->GetLocalAngles() ); pPlayer->m_Local.m_vecPunchAngle = vec3_angle; pPlayer->m_Local.m_vecPunchAngleVel = vec3_angle; pPlayer->SnapEyeAngles( pSpawnSpot->GetLocalAngles() ); return pSpawnSpot; } //----------------------------------------------------------------------------- // Purpose: Checks to see if the player is on the correct team and whether or // not the spawn point is available. //----------------------------------------------------------------------------- bool CTFGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer, bool bIgnorePlayers ) { // Check the team. if ( pSpot->GetTeamNumber() != pPlayer->GetTeamNumber() ) return false; if ( !pSpot->IsTriggered( pPlayer ) ) return false; CTFTeamSpawn *pCTFSpawn = dynamic_cast( pSpot ); if ( pCTFSpawn ) { if ( pCTFSpawn->IsDisabled() ) return false; } Vector mins = GetViewVectors()->m_vHullMin; Vector maxs = GetViewVectors()->m_vHullMax; if ( !bIgnorePlayers ) { Vector vTestMins = pSpot->GetAbsOrigin() + mins; Vector vTestMaxs = pSpot->GetAbsOrigin() + maxs; return UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs ); } trace_t trace; UTIL_TraceHull( pSpot->GetAbsOrigin(), pSpot->GetAbsOrigin(), mins, maxs, MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); return ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) ); } Vector CTFGameRules::VecItemRespawnSpot( CItem *pItem ) { return pItem->GetOriginalSpawnOrigin(); } QAngle CTFGameRules::VecItemRespawnAngles( CItem *pItem ) { return pItem->GetOriginalSpawnAngles(); } float CTFGameRules::FlItemRespawnTime( CItem *pItem ) { return ITEM_RESPAWN_TIME; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFGameRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ) { if ( !pPlayer ) // dedicated server output { return NULL; } const char *pszFormat = NULL; // team only if ( bTeamOnly == true ) { if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) { pszFormat = "TF_Chat_Spec"; } else { if ( pPlayer->IsAlive() == false && State_Get() != GR_STATE_TEAM_WIN ) { pszFormat = "TF_Chat_Team_Dead"; } else { const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer ); if ( chatLocation && *chatLocation ) { pszFormat = "TF_Chat_Team_Loc"; } else { pszFormat = "TF_Chat_Team"; } } } } // everyone else { if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) { pszFormat = "TF_Chat_AllSpec"; } else { if ( pPlayer->IsAlive() == false && State_Get() != GR_STATE_TEAM_WIN ) { pszFormat = "TF_Chat_AllDead"; } else { pszFormat = "TF_Chat_All"; } } } return pszFormat; } VoiceCommandMenuItem_t *CTFGameRules::VoiceCommand( CBaseMultiplayerPlayer *pPlayer, int iMenu, int iItem ) { VoiceCommandMenuItem_t *pItem = BaseClass::VoiceCommand( pPlayer, iMenu, iItem ); if ( pItem ) { int iActivity = ActivityList_IndexForName( pItem->m_szGestureActivity ); if ( iActivity != ACT_INVALID ) { CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer ); if ( pTFPlayer ) { pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_VOICE_COMMAND_GESTURE, iActivity ); } } } return pItem; } //----------------------------------------------------------------------------- // Purpose: Actually change a player's name. //----------------------------------------------------------------------------- void CTFGameRules::ChangePlayerName( CTFPlayer *pPlayer, const char *pszNewName ) { const char *pszOldName = pPlayer->GetPlayerName(); CReliableBroadcastRecipientFilter filter; UTIL_SayText2Filter( filter, pPlayer, false, "#TF_Name_Change", pszOldName, pszNewName ); IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" ); if ( event ) { event->SetInt( "userid", pPlayer->GetUserID() ); event->SetString( "oldname", pszOldName ); event->SetString( "newname", pszNewName ); gameeventmanager->FireEvent( event ); } pPlayer->SetPlayerName( pszNewName ); pPlayer->m_flNextNameChangeTime = gpGlobals->curtime + 10.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::ClientSettingsChanged( CBasePlayer *pPlayer ) { const char *pszName = engine->GetClientConVarValue( pPlayer->entindex(), "name" ); const char *pszOldName = pPlayer->GetPlayerName(); CTFPlayer *pTFPlayer = (CTFPlayer*)pPlayer; // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) // Note, not using FStrEq so that this is case sensitive if ( pszOldName[0] != 0 && Q_strncmp( pszOldName, pszName, MAX_PLAYER_NAME_LENGTH-1 ) ) { if ( pTFPlayer->m_flNextNameChangeTime < gpGlobals->curtime ) { ChangePlayerName( pTFPlayer, pszName ); } else { // no change allowed, force engine to use old name again engine->ClientCommand( pPlayer->edict(), "name \"%s\"", pszOldName ); // tell client that he hit the name change time limit ClientPrint( pTFPlayer, HUD_PRINTTALK, "#Name_change_limit_exceeded" ); } } // keep track of their hud_classautokill value int nClassAutoKill = Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "hud_classautokill" ) ); pTFPlayer->SetHudClassAutoKill( nClassAutoKill > 0 ? true : false ); // keep track of their tf_medigun_autoheal value pTFPlayer->SetMedigunAutoHeal( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "tf_medigun_autoheal" ) ) > 0 ); // keep track of their cl_autorezoom value pTFPlayer->SetAutoRezoom( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autorezoom" ) ) > 0 ); const char *pszFov = engine->GetClientConVarValue( pPlayer->entindex(), "fov_desired" ); int iFov = atoi(pszFov); iFov = clamp( iFov, 75, 90 ); pTFPlayer->SetDefaultFOV( iFov ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) { BaseClass::ClientCommandKeyValues( pEntity, pKeyValues ); CTFPlayer *pPlayer = ToTFPlayer( CBaseEntity::Instance( pEntity ) ); if ( !pPlayer ) return; if ( FStrEq( pKeyValues->GetName(), "FreezeCamTaunt" ) ) { int iAchieverIndex = pKeyValues->GetInt( "achiever" ); CTFPlayer *pAchiever = ToTFPlayer( UTIL_PlayerByIndex( iAchieverIndex ) ); if ( pAchiever && pAchiever != pPlayer ) { int iClass = pAchiever->GetPlayerClass()->GetClassIndex(); if ( g_TauntCamAchievements[iClass] != 0 ) { pAchiever->AwardAchievement( g_TauntCamAchievements[iClass] ); } } } } //----------------------------------------------------------------------------- // Purpose: Return true if the specified player can carry any more of the ammo type //----------------------------------------------------------------------------- bool CTFGameRules::CanHaveAmmo( CBaseCombatCharacter *pPlayer, int iAmmoIndex ) { if ( iAmmoIndex > -1 ) { CTFPlayer *pTFPlayer = (CTFPlayer*)pPlayer; if ( pTFPlayer ) { // Get the player class data - contains ammo counts for this class. TFPlayerClassData_t *pData = pTFPlayer->GetPlayerClass()->GetData(); if ( pData ) { // Get the max carrying capacity for this ammo int iMaxCarry = pData->m_aAmmoMax[iAmmoIndex]; // Does the player have room for more of this type of ammo? if ( pTFPlayer->GetAmmoCount( iAmmoIndex ) < iMaxCarry ) { return true; } } } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { // Find the killer & the scorer CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CBaseMultiplayerPlayer *pScorer = ToBaseMultiplayerPlayer( GetDeathScorer( pKiller, pInflictor, pVictim ) ); CTFPlayer *pAssister = NULL; CBaseObject *pObject = NULL; // if inflictor or killer is a base object, tell them that they got a kill // ( depends if a sentry rocket got the kill, sentry may be inflictor or killer ) if ( pInflictor ) { if ( pInflictor->IsBaseObject() ) { pObject = dynamic_cast( pInflictor ); } else { CBaseEntity *pInflictorOwner = pInflictor->GetOwnerEntity(); if ( pInflictorOwner && pInflictorOwner->IsBaseObject() ) { pObject = dynamic_cast( pInflictorOwner ); } } } else if( pKiller && pKiller->IsBaseObject() ) { pObject = dynamic_cast( pKiller ); } if ( pObject ) { pObject->IncrementKills(); pInflictor = pObject; if ( pObject->ObjectType() == OBJ_SENTRYGUN ) { CTFPlayer *pOwner = pObject->GetOwner(); if ( pOwner ) { int iKills = pObject->GetKills(); // keep track of max kills per a single sentry gun in the player object if ( pOwner->GetMaxSentryKills() < iKills ) { pOwner->SetMaxSentryKills( iKills ); CTF_GameStats.Event_MaxSentryKills( pOwner, iKills ); } // if we just got 10 kills with one sentry, tell the owner's client, which will award achievement if it doesn't have it already if ( iKills == 10 ) { pOwner->AwardAchievement( ACHIEVEMENT_TF_GET_TURRETKILLS ); } } } } // if not killed by suicide or killed by world, see if the scorer had an assister, and if so give the assister credit if ( ( pVictim != pScorer ) && pKiller ) { pAssister = ToTFPlayer( GetAssister( pVictim, pScorer, pInflictor ) ); } //find the area the player is in and see if his death causes a block CTriggerAreaCapture *pArea = dynamic_cast(gEntList.FindEntityByClassname( NULL, "trigger_capture_area" ) ); while( pArea ) { if ( pArea->CheckIfDeathCausesBlock( ToBaseMultiplayerPlayer(pVictim), pScorer ) ) break; pArea = dynamic_cast( gEntList.FindEntityByClassname( pArea, "trigger_capture_area" ) ); } // determine if this kill affected a nemesis relationship int iDeathFlags = 0; CTFPlayer *pTFPlayerVictim = ToTFPlayer( pVictim ); CTFPlayer *pTFPlayerScorer = ToTFPlayer( pScorer ); if ( pScorer ) { CalcDominationAndRevenge( pTFPlayerScorer, pTFPlayerVictim, false, &iDeathFlags ); if ( pAssister ) { CalcDominationAndRevenge( pAssister, pTFPlayerVictim, true, &iDeathFlags ); } } pTFPlayerVictim->SetDeathFlags( iDeathFlags ); if ( pAssister ) { CTF_GameStats.Event_AssistKill( ToTFPlayer( pAssister ), pVictim ); } BaseClass::PlayerKilled( pVictim, info ); } //----------------------------------------------------------------------------- // Purpose: Determines if attacker and victim have gotten domination or revenge //----------------------------------------------------------------------------- void CTFGameRules::CalcDominationAndRevenge( CTFPlayer *pAttacker, CTFPlayer *pVictim, bool bIsAssist, int *piDeathFlags ) { PlayerStats_t *pStatsVictim = CTF_GameStats.FindPlayerStats( pVictim ); // calculate # of unanswered kills between killer & victim - add 1 to include current kill int iKillsUnanswered = pStatsVictim->statsKills.iNumKilledByUnanswered[pAttacker->entindex()] + 1; if ( TF_KILLS_DOMINATION == iKillsUnanswered ) { // this is the Nth unanswered kill between killer and victim, killer is now dominating victim *piDeathFlags |= ( bIsAssist ? TF_DEATH_ASSISTER_DOMINATION : TF_DEATH_DOMINATION ); // set victim to be dominated by killer pAttacker->m_Shared.SetPlayerDominated( pVictim, true ); // record stats CTF_GameStats.Event_PlayerDominatedOther( pAttacker ); } else if ( pVictim->m_Shared.IsPlayerDominated( pAttacker->entindex() ) ) { // the killer killed someone who was dominating him, gains revenge *piDeathFlags |= ( bIsAssist ? TF_DEATH_ASSISTER_REVENGE : TF_DEATH_REVENGE ); // set victim to no longer be dominating the killer pVictim->m_Shared.SetPlayerDominated( pAttacker, false ); // record stats CTF_GameStats.Event_PlayerRevenge( pAttacker ); } } //----------------------------------------------------------------------------- // Purpose: create some proxy entities that we use for transmitting data */ //----------------------------------------------------------------------------- void CTFGameRules::CreateStandardEntities() { // Create the player resource g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "tf_player_manager", vec3_origin, vec3_angle ); // Create the objective resource g_pObjectiveResource = (CTFObjectiveResource *)CBaseEntity::Create( "tf_objective_resource", vec3_origin, vec3_angle ); Assert( g_pObjectiveResource ); // Create the entity that will send our data to the client. CBaseEntity *pEnt = CBaseEntity::Create( "tf_gamerules", vec3_origin, vec3_angle ); Assert( pEnt ); pEnt->SetName( AllocPooledString("tf_gamerules" ) ); } //----------------------------------------------------------------------------- // Purpose: determine the class name of the weapon that got a kill //----------------------------------------------------------------------------- const char *CTFGameRules::GetKillingWeaponName( const CTakeDamageInfo &info, CTFPlayer *pVictim ) { CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CBasePlayer *pScorer = TFGameRules()->GetDeathScorer( pKiller, pInflictor, pVictim ); const char *killer_weapon_name = "world"; if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BURNING ) { // special-case burning damage, since persistent burning damage may happen after attacker has switched weapons killer_weapon_name = "tf_weapon_flamethrower"; } else if ( pScorer && pInflictor && ( pInflictor == pScorer ) ) { // If the inflictor is the killer, then it must be their current weapon doing the damage if ( pScorer->GetActiveWeapon() ) { killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); } } else if ( pInflictor ) { killer_weapon_name = STRING( pInflictor->m_iClassname ); } // strip certain prefixes from inflictor's classname const char *prefix[] = { "tf_weapon_grenade_", "tf_weapon_", "NPC_", "func_" }; for ( int i = 0; i< ARRAYSIZE( prefix ); i++ ) { // if prefix matches, advance the string pointer past the prefix int len = Q_strlen( prefix[i] ); if ( strncmp( killer_weapon_name, prefix[i], len ) == 0 ) { killer_weapon_name += len; break; } } // look out for sentry rocket as weapon and map it to sentry gun, so we get the sentry death icon if ( 0 == Q_strcmp( killer_weapon_name, "tf_projectile_sentryrocket" ) ) { killer_weapon_name = "obj_sentrygun"; } return killer_weapon_name; } //----------------------------------------------------------------------------- // Purpose: returns the player who assisted in the kill, or NULL if no assister //----------------------------------------------------------------------------- CBasePlayer *CTFGameRules::GetAssister( CBasePlayer *pVictim, CBasePlayer *pScorer, CBaseEntity *pInflictor ) { CTFPlayer *pTFScorer = ToTFPlayer( pScorer ); CTFPlayer *pTFVictim = ToTFPlayer( pVictim ); if ( pTFScorer && pTFVictim ) { // if victim killed himself, don't award an assist to anyone else, even if there was a recent damager if ( pTFScorer == pTFVictim ) return NULL; // If a player is healing the scorer, give that player credit for the assist CTFPlayer *pHealer = ToTFPlayer( static_cast( pTFScorer->m_Shared.GetFirstHealer() ) ); // Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing. // Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills. if ( pHealer && ( TF_CLASS_MEDIC == pHealer->GetPlayerClass()->GetClassIndex() ) && ( NULL == dynamic_cast( pInflictor ) ) ) { return pHealer; } // See who has damaged the victim 2nd most recently (most recent is the killer), and if that is within a certain time window. // If so, give that player an assist. (Only 1 assist granted, to single other most recent damager.) CTFPlayer *pRecentDamager = GetRecentDamager( pTFVictim, 1, TF_TIME_ASSIST_KILL ); if ( pRecentDamager && ( pRecentDamager != pScorer ) ) return pRecentDamager; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns specifed recent damager, if there is one who has done damage // within the specified time period. iDamager=0 returns the most recent // damager, iDamager=1 returns the next most recent damager. //----------------------------------------------------------------------------- CTFPlayer *CTFGameRules::GetRecentDamager( CTFPlayer *pVictim, int iDamager, float flMaxElapsed ) { Assert( iDamager < MAX_DAMAGER_HISTORY ); DamagerHistory_t &damagerHistory = pVictim->GetDamagerHistory( iDamager ); if ( ( NULL != damagerHistory.hDamager ) && ( gpGlobals->curtime - damagerHistory.flTimeDamage <= flMaxElapsed ) ) { CTFPlayer *pRecentDamager = ToTFPlayer( damagerHistory.hDamager ); if ( pRecentDamager ) return pRecentDamager; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns who should be awarded the kill //----------------------------------------------------------------------------- CBasePlayer *CTFGameRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor, CBaseEntity *pVictim ) { if ( ( pKiller == pVictim ) && ( pKiller == pInflictor ) ) { // If this was an explicit suicide, see if there was a damager within a certain time window. If so, award this as a kill to the damager. CTFPlayer *pTFVictim = ToTFPlayer( pVictim ); if ( pTFVictim ) { CTFPlayer *pRecentDamager = GetRecentDamager( pTFVictim, 0, TF_TIME_SUICIDE_KILL_CREDIT ); if ( pRecentDamager ) return pRecentDamager; } } return BaseClass::GetDeathScorer( pKiller, pInflictor, pVictim ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pVictim - // *pKiller - // *pInflictor - //----------------------------------------------------------------------------- void CTFGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { int killer_ID = 0; // Find the killer & the scorer CTFPlayer *pTFPlayerVictim = ToTFPlayer( pVictim ); CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor, pVictim ); CTFPlayer *pAssister = ToTFPlayer( GetAssister( pVictim, pScorer, pInflictor ) ); // Work out what killed the player, and send a message to all clients about it const char *killer_weapon_name = GetKillingWeaponName( info, pTFPlayerVictim ); if ( pScorer ) // Is the killer a client? { killer_ID = pScorer->GetUserID(); } IGameEvent * event = gameeventmanager->CreateEvent( "player_death" ); if ( event ) { event->SetInt( "userid", pVictim->GetUserID() ); event->SetInt( "attacker", killer_ID ); event->SetInt( "assister", pAssister ? pAssister->GetUserID() : -1 ); event->SetString( "weapon", killer_weapon_name ); event->SetInt( "damagebits", info.GetDamageType() ); event->SetInt( "customkill", info.GetDamageCustom() ); event->SetInt( "priority", 7 ); // HLTV event priority, not transmitted if ( pTFPlayerVictim->GetDeathFlags() & TF_DEATH_DOMINATION ) { event->SetInt( "dominated", 1 ); } if ( pTFPlayerVictim->GetDeathFlags() & TF_DEATH_ASSISTER_DOMINATION ) { event->SetInt( "assister_dominated", 1 ); } if ( pTFPlayerVictim->GetDeathFlags() & TF_DEATH_REVENGE ) { event->SetInt( "revenge", 1 ); } if ( pTFPlayerVictim->GetDeathFlags() & TF_DEATH_ASSISTER_REVENGE ) { event->SetInt( "assister_revenge", 1 ); } gameeventmanager->FireEvent( event ); } } void CTFGameRules::ClientDisconnected( edict_t *pClient ) { // clean up anything they left behind CTFPlayer *pPlayer = ToTFPlayer( GetContainingEntity( pClient ) ); if ( pPlayer ) { pPlayer->TeamFortress_ClientDisconnected(); } // are any of the spies disguising as this player? for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CTFPlayer *pTemp = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pTemp && pTemp != pPlayer ) { if ( pTemp->m_Shared.GetDisguiseTarget() == pPlayer ) { // choose someone else... pTemp->m_Shared.FindDisguiseTarget(); } } } BaseClass::ClientDisconnected( pClient ); } // Falling damage stuff. #define TF_PLAYER_MAX_SAFE_FALL_SPEED 650 float CTFGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) { if ( pPlayer->m_Local.m_flFallVelocity > TF_PLAYER_MAX_SAFE_FALL_SPEED ) { // Old TFC damage formula float flFallDamage = 5 * (pPlayer->m_Local.m_flFallVelocity / 300); // Fall damage needs to scale according to the player's max health, or // it's always going to be much more dangerous to weaker classes than larger. float flRatio = (float)pPlayer->GetMaxHealth() / 100.0; flFallDamage *= flRatio; flFallDamage *= random->RandomFloat( 0.8, 1.2 ); return flFallDamage; } // Fall caused no damage return 0; } void CTFGameRules::SendWinPanelInfo( void ) { IGameEvent *winEvent = gameeventmanager->CreateEvent( "teamplay_win_panel" ); if ( winEvent ) { int iBlueScore = GetGlobalTeam( TF_TEAM_BLUE )->GetScore(); int iRedScore = GetGlobalTeam( TF_TEAM_RED )->GetScore(); int iBlueScorePrev = iBlueScore; int iRedScorePrev = iRedScore; bool bRoundComplete = m_bForceMapReset || ( IsGameUnderTimeLimit() && ( GetTimeLeft() <= 0 ) ); CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; bool bScoringPerCapture = ( pMaster ) ? ( pMaster->ShouldScorePerCapture() ) : false; if ( bRoundComplete && !bScoringPerCapture ) { // if this is a complete round, calc team scores prior to this win switch ( m_iWinningTeam ) { case TF_TEAM_BLUE: iBlueScorePrev = ( iBlueScore - TEAMPLAY_ROUND_WIN_SCORE >= 0 ) ? ( iBlueScore - TEAMPLAY_ROUND_WIN_SCORE ) : 0; break; case TF_TEAM_RED: iRedScorePrev = ( iRedScore - TEAMPLAY_ROUND_WIN_SCORE >= 0 ) ? ( iRedScore - TEAMPLAY_ROUND_WIN_SCORE ) : 0; break; case TEAM_UNASSIGNED: break; // stalemate; nothing to do } } winEvent->SetInt( "panel_style", WINPANEL_BASIC ); winEvent->SetInt( "winning_team", m_iWinningTeam ); winEvent->SetInt( "winreason", m_iWinReason ); winEvent->SetString( "cappers", ( m_iWinReason == WINREASON_ALL_POINTS_CAPTURED || m_iWinReason == WINREASON_FLAG_CAPTURE_LIMIT ) ? m_szMostRecentCappers : "" ); winEvent->SetInt( "flagcaplimit", tf_flag_caps_per_round.GetInt() ); winEvent->SetInt( "blue_score", iBlueScore ); winEvent->SetInt( "red_score", iRedScore ); winEvent->SetInt( "blue_score_prev", iBlueScorePrev ); winEvent->SetInt( "red_score_prev", iRedScorePrev ); winEvent->SetInt( "round_complete", bRoundComplete ); CTFPlayerResource *pResource = dynamic_cast< CTFPlayerResource * >( g_pPlayerResource ); if ( !pResource ) return; // determine the 3 players on winning team who scored the most points this round // build a vector of players & round scores CUtlVector vecPlayerScore; int iPlayerIndex; for( iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) { CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); if ( !pTFPlayer || !pTFPlayer->IsConnected() ) continue; // filter out spectators and, if not stalemate, all players not on winning team int iPlayerTeam = pTFPlayer->GetTeamNumber(); if ( ( iPlayerTeam < FIRST_GAME_TEAM ) || ( m_iWinningTeam != TEAM_UNASSIGNED && ( m_iWinningTeam != iPlayerTeam ) ) ) continue; int iRoundScore = 0, iTotalScore = 0; PlayerStats_t *pStats = CTF_GameStats.FindPlayerStats( pTFPlayer ); if ( pStats ) { iRoundScore = CalcPlayerScore( &pStats->statsCurrentRound ); iTotalScore = CalcPlayerScore( &pStats->statsAccumulated ); } PlayerRoundScore_t &playerRoundScore = vecPlayerScore[vecPlayerScore.AddToTail()]; playerRoundScore.iPlayerIndex = iPlayerIndex; playerRoundScore.iRoundScore = iRoundScore; playerRoundScore.iTotalScore = iTotalScore; } // sort the players by round score vecPlayerScore.Sort( PlayerRoundScoreSortFunc ); // set the top (up to) 3 players by round score in the event data int numPlayers = MIN( 3, vecPlayerScore.Count() ); for ( int i = 0; i < numPlayers; i++ ) { // only include players who have non-zero points this round; if we get to a player with 0 round points, stop if ( 0 == vecPlayerScore[i].iRoundScore ) break; // set the player index and their round score in the event char szPlayerIndexVal[64]="", szPlayerScoreVal[64]=""; Q_snprintf( szPlayerIndexVal, ARRAYSIZE( szPlayerIndexVal ), "player_%d", i+ 1 ); Q_snprintf( szPlayerScoreVal, ARRAYSIZE( szPlayerScoreVal ), "player_%d_points", i+ 1 ); winEvent->SetInt( szPlayerIndexVal, vecPlayerScore[i].iPlayerIndex ); winEvent->SetInt( szPlayerScoreVal, vecPlayerScore[i].iRoundScore ); } if ( !bRoundComplete && ( TEAM_UNASSIGNED != m_iWinningTeam ) ) { // if this was not a full round ending, include how many mini-rounds remain for winning team to win if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] ) { winEvent->SetInt( "rounds_remaining", g_hControlPointMasters[0]->CalcNumRoundsRemaining( m_iWinningTeam ) ); } } // Send the event gameeventmanager->FireEvent( winEvent ); } } //----------------------------------------------------------------------------- // Purpose: Sorts players by round score //----------------------------------------------------------------------------- int CTFGameRules::PlayerRoundScoreSortFunc( const PlayerRoundScore_t *pRoundScore1, const PlayerRoundScore_t *pRoundScore2 ) { // sort first by round score if ( pRoundScore1->iRoundScore != pRoundScore2->iRoundScore ) return pRoundScore2->iRoundScore - pRoundScore1->iRoundScore; // if round scores are the same, sort next by total score if ( pRoundScore1->iTotalScore != pRoundScore2->iTotalScore ) return pRoundScore2->iTotalScore - pRoundScore1->iTotalScore; // if scores are the same, sort next by player index so we get deterministic sorting return ( pRoundScore2->iPlayerIndex - pRoundScore1->iPlayerIndex ); } //----------------------------------------------------------------------------- // Purpose: Called when the teamplay_round_win event is about to be sent, gives // this method a chance to add more data to it //----------------------------------------------------------------------------- void CTFGameRules::FillOutTeamplayRoundWinEvent( IGameEvent *event ) { // determine the losing team int iLosingTeam; switch( event->GetInt( "team" ) ) { case TF_TEAM_RED: iLosingTeam = TF_TEAM_BLUE; break; case TF_TEAM_BLUE: iLosingTeam = TF_TEAM_RED; break; case TEAM_UNASSIGNED: default: iLosingTeam = TEAM_UNASSIGNED; break; } // set the number of caps that team got any time during the round event->SetInt( "losing_team_num_caps", m_iNumCaps[iLosingTeam] ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::SetupSpawnPointsForRound( void ) { if ( !g_hControlPointMasters.Count() || !g_hControlPointMasters[0] || !g_hControlPointMasters[0]->PlayingMiniRounds() ) return; CTeamControlPointRound *pCurrentRound = g_hControlPointMasters[0]->GetCurrentRound(); if ( !pCurrentRound ) { return; } // loop through the spawn points in the map and find which ones are associated with this round or the control points in this round CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_teamspawn" ); while( pSpot ) { CTFTeamSpawn *pTFSpawn = assert_cast(pSpot); if ( pTFSpawn ) { CHandle hControlPoint = pTFSpawn->GetControlPoint(); CHandle hRoundBlue = pTFSpawn->GetRoundBlueSpawn(); CHandle hRoundRed = pTFSpawn->GetRoundRedSpawn(); if ( hControlPoint && pCurrentRound->IsControlPointInRound( hControlPoint ) ) { // this spawn is associated with one of our control points pTFSpawn->SetDisabled( false ); pTFSpawn->ChangeTeam( hControlPoint->GetOwner() ); } else if ( hRoundBlue && ( hRoundBlue == pCurrentRound ) ) { pTFSpawn->SetDisabled( false ); pTFSpawn->ChangeTeam( TF_TEAM_BLUE ); } else if ( hRoundRed && ( hRoundRed == pCurrentRound ) ) { pTFSpawn->SetDisabled( false ); pTFSpawn->ChangeTeam( TF_TEAM_RED ); } else { // this spawn isn't associated with this round or the control points in this round pTFSpawn->SetDisabled( true ); } } pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_teamspawn" ); } } int CTFGameRules::SetCurrentRoundStateBitString( void ) { m_iPrevRoundState = m_iCurrentRoundState; CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; if ( !pMaster ) { return 0; } int iState = 0; for ( int i=0; iGetNumPoints(); i++ ) { CTeamControlPoint *pPoint = pMaster->GetControlPoint( i ); if ( pPoint->GetOwner() == TF_TEAM_BLUE ) { // Set index to 1 for the point being owned by blue iState |= ( 1<= 0 && pPlayer == NULL ) // we have data about a previous state { data->SetInt( "prev", m_iPrevRoundState ); } else { // don't send a delta if this is just to one player, they are joining mid-round data->SetInt( "prev", m_iCurrentRoundState ); } data->SetInt( "cur", m_iCurrentRoundState ); // get bitmask representing the current miniround data->SetInt( "round", m_iCurrentMiniRoundMask ); if ( pPlayer ) { pPlayer->ShowViewPortPanel( PANEL_ROUNDINFO, true, data ); } else { for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pTFPlayer && pTFPlayer->IsReadyToPlay() ) { pTFPlayer->ShowViewPortPanel( PANEL_ROUNDINFO, true, data ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::TimerMayExpire( void ) { // Prevent timers expiring while control points are contested int iNumControlPoints = ObjectiveResource()->GetNumControlPoints(); for ( int iPoint = 0; iPoint < iNumControlPoints; iPoint++ ) { if ( ObjectiveResource()->GetCappingTeam( iPoint ) ) { // HACK: Fix for some maps adding time to the clock 0.05s after CP is capped. m_flTimerMayExpireAt = gpGlobals->curtime + 0.1f; return false; } } if ( m_flTimerMayExpireAt >= gpGlobals->curtime ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::RoundRespawn( void ) { // remove any buildings, grenades, rockets, etc. the player put into the world for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { pPlayer->TeamFortress_RemoveEverythingFromWorld(); } } // reset the flag captures int nTeamCount = TFTeamMgr()->GetTeamCount(); for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam ) { CTFTeam *pTeam = GetGlobalTFTeam( iTeam ); if ( !pTeam ) continue; pTeam->SetFlagCaptures( 0 ); } CTF_GameStats.ResetRoundStats(); BaseClass::RoundRespawn(); // ** AFTER WE'VE BEEN THROUGH THE ROUND RESPAWN, SHOW THE ROUNDINFO PANEL if ( !IsInWaitingForPlayers() ) { ShowRoundInfoPanel(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::InternalHandleTeamWin( int iWinningTeam ) { // remove any spies' disguises and make them visible (for the losing team only) // and set the speed for both teams (winners get a boost and losers have reduced speed) for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { if ( pPlayer->GetTeamNumber() > LAST_SHARED_TEAM ) { if ( pPlayer->GetTeamNumber() != iWinningTeam ) { pPlayer->RemoveInvisibility(); // pPlayer->RemoveDisguise(); if ( pPlayer->HasTheFlag() ) { pPlayer->DropFlag(); } } pPlayer->TeamFortress_SetSpeed(); } } } // disable any sentry guns the losing team has built CBaseEntity *pEnt = NULL; while ( ( pEnt = gEntList.FindEntityByClassname( pEnt, "obj_sentrygun" ) ) != NULL ) { CObjectSentrygun *pSentry = dynamic_cast( pEnt ); if ( pSentry ) { if ( pSentry->GetTeamNumber() != iWinningTeam ) { pSentry->SetDisabled( true ); } } } if ( m_bForceMapReset ) { m_iPrevRoundState = -1; m_iCurrentRoundState = -1; m_iCurrentMiniRoundMask = 0; } } // sort function for the list of players that we're going to use to scramble the teams int ScramblePlayersSort( CTFPlayer* const *p1, CTFPlayer* const *p2 ) { CTFPlayerResource *pResource = dynamic_cast< CTFPlayerResource * >( g_pPlayerResource ); if ( pResource ) { // check the priority if ( pResource->GetTotalScore( (*p2)->entindex() ) > pResource->GetTotalScore( (*p1)->entindex() ) ) { return 1; } } return -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::HandleScrambleTeams( void ) { int i = 0; CTFPlayer *pTFPlayer = NULL; CUtlVector pListPlayers; // add all the players (that are on blue or red) to our temp list for ( i = 1 ; i <= gpGlobals->maxClients ; i++ ) { pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pTFPlayer && ( pTFPlayer->GetTeamNumber() >= TF_TEAM_RED ) ) { pListPlayers.AddToHead( pTFPlayer ); } } // sort the list pListPlayers.Sort( ScramblePlayersSort ); // loop through and put everyone on Spectator to clear the teams (or the autoteam step won't work correctly) for ( i = 0 ; i < pListPlayers.Count() ; i++ ) { pTFPlayer = pListPlayers[i]; if ( pTFPlayer ) { pTFPlayer->ForceChangeTeam( TEAM_SPECTATOR ); } } // loop through and auto team everyone for ( i = 0 ; i < pListPlayers.Count() ; i++ ) { pTFPlayer = pListPlayers[i]; if ( pTFPlayer ) { pTFPlayer->ForceChangeTeam( TF_TEAM_AUTOASSIGN ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::HandleSwitchTeams( void ) { int i = 0; // respawn the players for ( i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { pPlayer->TeamFortress_RemoveEverythingFromWorld(); // Ignore players who aren't on an active team if ( pPlayer->GetTeamNumber() != TF_TEAM_RED && pPlayer->GetTeamNumber() != TF_TEAM_BLUE ) { continue; } if ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) { pPlayer->ForceChangeTeam( TF_TEAM_BLUE ); } else if ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) { pPlayer->ForceChangeTeam( TF_TEAM_RED ); } } } // switch the team scores CTFTeam *pRedTeam = GetGlobalTFTeam( TF_TEAM_RED ); CTFTeam *pBlueTeam = GetGlobalTFTeam( TF_TEAM_BLUE ); if ( pRedTeam && pBlueTeam ) { int nRed = pRedTeam->GetScore(); int nBlue = pBlueTeam->GetScore(); pRedTeam->SetScore( nBlue ); pBlueTeam->SetScore( nRed ); } } bool CTFGameRules::CanChangeClassInStalemate( void ) { return (gpGlobals->curtime < (m_flStalemateStartTime + tf_stalematechangeclasstime.GetFloat())); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::SetRoundOverlayDetails( void ) { CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; if ( pMaster && pMaster->PlayingMiniRounds() ) { CTeamControlPointRound *pRound = pMaster->GetCurrentRound(); if ( pRound ) { CHandle pRedPoint = pRound->GetPointOwnedBy( TF_TEAM_RED ); CHandle pBluePoint = pRound->GetPointOwnedBy( TF_TEAM_BLUE ); // do we have opposing points in this round? if ( pRedPoint && pBluePoint ) { int iMiniRoundMask = ( 1<GetPointIndex() ) | ( 1<GetPointIndex() ); SetMiniRoundBitMask( iMiniRoundMask ); } else { SetMiniRoundBitMask( 0 ); } SetCurrentRoundStateBitString(); } } BaseClass::SetRoundOverlayDetails(); } //----------------------------------------------------------------------------- // Purpose: Returns whether a team should score for each captured point //----------------------------------------------------------------------------- bool CTFGameRules::ShouldScorePerRound( void ) { bool bRetVal = true; CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; if ( pMaster && pMaster->ShouldScorePerCapture() ) { bRetVal = false; } return bRetVal; } #endif // GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFGameRules::GetFarthestOwnedControlPoint( int iTeam, bool bWithSpawnpoints ) { int iOwnedEnd = ObjectiveResource()->GetBaseControlPointForTeam( iTeam ); if ( iOwnedEnd == -1 ) return -1; int iNumControlPoints = ObjectiveResource()->GetNumControlPoints(); int iWalk = 1; int iEnemyEnd = iNumControlPoints-1; if ( iOwnedEnd != 0 ) { iWalk = -1; iEnemyEnd = 0; } // Walk towards the other side, and find the farthest owned point that has spawn points int iFarthestPoint = iOwnedEnd; for ( int iPoint = iOwnedEnd; iPoint != iEnemyEnd; iPoint += iWalk ) { // If we've hit a point we don't own, we're done if ( ObjectiveResource()->GetOwningTeam( iPoint ) != iTeam ) break; if ( bWithSpawnpoints && !m_bControlSpawnsPerTeam[iTeam][iPoint] ) continue; iFarthestPoint = iPoint; } return iFarthestPoint; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::TeamMayCapturePoint( int iTeam, int iPointIndex ) { if ( !tf_caplinear.GetBool() ) return true; // Any previous points necessary? int iPointNeeded = ObjectiveResource()->GetPreviousPointForPoint( iPointIndex, iTeam, 0 ); // Points set to require themselves are always cappable if ( iPointNeeded == iPointIndex ) return true; // No required points specified? Require all previous points. if ( iPointNeeded == -1 ) { if ( !ObjectiveResource()->PlayingMiniRounds() ) { // No custom previous point, team must own all previous points int iFarthestPoint = GetFarthestOwnedControlPoint( iTeam, false ); return (abs(iFarthestPoint - iPointIndex) <= 1); } else { // No custom previous point, team must own all previous points in the current mini-round //tagES TFTODO: need to figure out a good algorithm for this return true; } } // Loop through each previous point and see if the team owns it for ( int iPrevPoint = 0; iPrevPoint < MAX_PREVIOUS_POINTS; iPrevPoint++ ) { int iPointNeeded = ObjectiveResource()->GetPreviousPointForPoint( iPointIndex, iTeam, iPrevPoint ); if ( iPointNeeded != -1 ) { if ( ObjectiveResource()->GetOwningTeam( iPointNeeded ) != iTeam ) return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::PlayerMayCapturePoint( CBasePlayer *pPlayer, int iPointIndex, char *pszReason /* = NULL */, int iMaxReasonLength /* = 0 */ ) { CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer ); if ( !pTFPlayer ) { return false; } // Disguised and invisible spies cannot capture points if ( pTFPlayer->m_Shared.InCond( TF_COND_STEALTHED ) ) { if ( pszReason ) { Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_stealthed" ); } return false; } if ( pTFPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) ) { if ( pszReason ) { Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_invuln" ); } return false; } if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) { if ( pszReason ) { Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_disguised" ); } return false; } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::PlayerMayBlockPoint( CBasePlayer *pPlayer, int iPointIndex, char *pszReason, int iMaxReasonLength ) { CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer ); if ( !pTFPlayer ) return false; // Invuln players can block points if ( pTFPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) ) { if ( pszReason ) { Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_invuln" ); } return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Calculates score for player //----------------------------------------------------------------------------- int CTFGameRules::CalcPlayerScore( RoundStats_t *pRoundStats ) { int iScore = ( pRoundStats->m_iStat[TFSTAT_KILLS] * TF_SCORE_KILL ) + ( pRoundStats->m_iStat[TFSTAT_CAPTURES] * TF_SCORE_CAPTURE ) + ( pRoundStats->m_iStat[TFSTAT_DEFENSES] * TF_SCORE_DEFEND ) + ( pRoundStats->m_iStat[TFSTAT_BUILDINGSDESTROYED] * TF_SCORE_DESTROY_BUILDING ) + ( pRoundStats->m_iStat[TFSTAT_HEADSHOTS] * TF_SCORE_HEADSHOT ) + ( pRoundStats->m_iStat[TFSTAT_BACKSTABS] * TF_SCORE_BACKSTAB ) + ( pRoundStats->m_iStat[TFSTAT_HEALING] / TF_SCORE_HEAL_HEALTHUNITS_PER_POINT ) + ( pRoundStats->m_iStat[TFSTAT_KILLASSISTS] / TF_SCORE_KILL_ASSISTS_PER_POINT ) + ( pRoundStats->m_iStat[TFSTAT_TELEPORTS] / TF_SCORE_TELEPORTS_PER_POINT ) + ( pRoundStats->m_iStat[TFSTAT_INVULNS] / TF_SCORE_INVULN ) + ( pRoundStats->m_iStat[TFSTAT_REVENGE] / TF_SCORE_REVENGE ); return MAX( iScore, 0 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::IsBirthday( void ) { if ( IsX360() ) return false; if ( m_iBirthdayMode == BIRTHDAY_RECALCULATE ) { m_iBirthdayMode = BIRTHDAY_OFF; if ( tf_birthday.GetBool() ) { m_iBirthdayMode = BIRTHDAY_ON; } else { time_t ltime = time(0); const time_t *ptime = <ime; struct tm *today = localtime( ptime ); if ( today ) { if ( today->tm_mon == 7 && today->tm_mday == 24 ) { m_iBirthdayMode = BIRTHDAY_ON; } } } } return ( m_iBirthdayMode == BIRTHDAY_ON ); } ConVar tf_is_mann_vs_machine_mode( "tf_is_mann_vs_machine_mode", "0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN ); ConVar tf_allow_training_achievements( "tf_allow_training_achievements", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN ); //----------------------------------------------------------------------------- // Purpose: FIXME: stub //----------------------------------------------------------------------------- bool CTFGameRules::IsMannVsMachineMode( void ) { return tf_is_mann_vs_machine_mode.GetInt() ? true : false; } //----------------------------------------------------------------------------- // Purpose: FIXME: stub //----------------------------------------------------------------------------- bool CTFGameRules::AllowTrainingAchievements( void ) { return tf_allow_training_achievements.GetInt() ? true : false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::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; } // Don't stand on projectiles if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT && collisionGroup1 == COLLISION_GROUP_PROJECTILE ) { return false; } // Rockets need to collide with players when they hit, but // be ignored by player movement checks if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER ) && ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS ) ) return true; if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ) && ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS ) ) return false; if ( ( collisionGroup0 == COLLISION_GROUP_WEAPON ) && ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS ) ) return false; if ( ( collisionGroup0 == TF_COLLISIONGROUP_GRENADES ) && ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS ) ) return false; // Grenades don't collide with players. They handle collision while flying around manually. if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER ) && ( collisionGroup1 == TF_COLLISIONGROUP_GRENADES ) ) return false; if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ) && ( collisionGroup1 == TF_COLLISIONGROUP_GRENADES ) ) return false; // Respawn rooms only collide with players if ( collisionGroup1 == TFCOLLISION_GROUP_RESPAWNROOMS ) return ( collisionGroup0 == COLLISION_GROUP_PLAYER ) || ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ); /* if ( collisionGroup0 == COLLISION_GROUP_PLAYER ) { // Players don't collide with objects or other players if ( collisionGroup1 == COLLISION_GROUP_PLAYER ) return false; } if ( collisionGroup1 == COLLISION_GROUP_PLAYER_MOVEMENT ) { // This is only for probing, so it better not be on both sides!!! Assert( collisionGroup0 != COLLISION_GROUP_PLAYER_MOVEMENT ); // No collide with players any more // Nor with objects or grenades switch ( collisionGroup0 ) { default: break; case COLLISION_GROUP_PLAYER: return false; } } */ // don't want caltrops and other grenades colliding with each other // caltops getting stuck on other caltrops, etc.) if ( ( collisionGroup0 == TF_COLLISIONGROUP_GRENADES ) && ( collisionGroup1 == TF_COLLISIONGROUP_GRENADES ) ) { return false; } if ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT && collisionGroup1 == TFCOLLISION_GROUP_COMBATOBJECT ) { return false; } if ( collisionGroup0 == COLLISION_GROUP_PLAYER && collisionGroup1 == TFCOLLISION_GROUP_COMBATOBJECT ) { return false; } return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); } //----------------------------------------------------------------------------- // Purpose: Return the value of this player towards capturing a point //----------------------------------------------------------------------------- int CTFGameRules::GetCaptureValueForPlayer( CBasePlayer *pPlayer ) { CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer ); if ( pTFPlayer->IsPlayerClass( TF_CLASS_SCOUT ) ) { if ( mp_capstyle.GetInt() == 1 ) { // Scouts count for 2 people in timebased capping. return 2; } else { // Scouts can cap all points on their own. return 10; } } return BaseClass::GetCaptureValueForPlayer( pPlayer ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFGameRules::GetTimeLeft( void ) { float flTimeLimit = mp_timelimit.GetInt() * 60; Assert( flTimeLimit > 0 && "Should not call this function when !IsGameUnderTimeLimit" ); float flMapChangeTime = m_flMapResetTime + flTimeLimit; return ( (int)(flMapChangeTime - gpGlobals->curtime) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::FireGameEvent( IGameEvent *event ) { const char *eventName = event->GetName(); if ( !Q_strcmp( eventName, "teamplay_point_captured" ) ) { #ifdef GAME_DLL RecalculateControlPointState(); // keep track of how many times each team caps int iTeam = event->GetInt( "team" ); Assert( iTeam >= FIRST_GAME_TEAM && iTeam < TF_TEAM_COUNT ); m_iNumCaps[iTeam]++; // award a capture to all capping players const char *cappers = event->GetString( "cappers" ); Q_strncpy( m_szMostRecentCappers, cappers, ARRAYSIZE( m_szMostRecentCappers ) ); for ( int i =0; i < Q_strlen( cappers ); i++ ) { int iPlayerIndex = (int) cappers[i]; CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); if ( pPlayer ) { CTF_GameStats.Event_PlayerCapturedPoint( pPlayer ); } } #endif } else if ( !Q_strcmp( eventName, "teamplay_capture_blocked" ) ) { #ifdef GAME_DLL int iPlayerIndex = event->GetInt( "blocker" ); CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); CTF_GameStats.Event_PlayerDefendedPoint( pPlayer ); #endif } else if ( !Q_strcmp( eventName, "teamplay_round_win" ) ) { #ifdef GAME_DLL int iWinningTeam = event->GetInt( "team" ); bool bFullRound = event->GetBool( "full_round" ); float flRoundTime = event->GetFloat( "round_time" ); bool bWasSuddenDeath = event->GetBool( "was_sudden_death" ); CTF_GameStats.Event_RoundEnd( iWinningTeam, bFullRound, flRoundTime, bWasSuddenDeath ); #endif } else if ( !Q_strcmp( eventName, "teamplay_flag_event" ) ) { #ifdef GAME_DLL // if this is a capture event, remember the player who made the capture int iEventType = event->GetInt( "eventtype" ); if ( TF_FLAGEVENT_CAPTURE == iEventType ) { int iPlayerIndex = event->GetInt( "player" ); m_szMostRecentCappers[0] = iPlayerIndex; m_szMostRecentCappers[1] = 0; } #endif } #ifdef CLIENT_DLL else if ( !Q_strcmp( eventName, "game_newmap" ) ) { m_iBirthdayMode = BIRTHDAY_RECALCULATE; } #endif } //----------------------------------------------------------------------------- // Purpose: Init ammo definitions //----------------------------------------------------------------------------- // shared ammo definition // JAY: Trying to make a more physical bullet response #define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) #define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) // exaggerate all of the forces, but use real numbers to keep them consistent #define BULLET_IMPULSE_EXAGGERATION 1 // convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s #define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) CAmmoDef* GetAmmoDef() { static CAmmoDef def; static bool bInitted = false; if ( !bInitted ) { bInitted = true; // Start at 1 here and skip the dummy ammo type to make CAmmoDef use the same indices // as our #defines. for ( int i=1; i < TF_AMMO_COUNT; i++ ) { def.AddAmmoType( g_aAmmoNames[i], DMG_BULLET, TRACER_LINE, 0, 0, "ammo_max", 2400, 10, 14 ); Assert( def.Index( g_aAmmoNames[i] ) == i ); } } return &def; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFGameRules::GetTeamGoalString( int iTeam ) { if ( iTeam == TF_TEAM_RED ) return m_pszTeamGoalStringRed.Get(); if ( iTeam == TF_TEAM_BLUE ) return m_pszTeamGoalStringBlue.Get(); return NULL; } #ifdef GAME_DLL Vector MaybeDropToGround( CBaseEntity *pMainEnt, bool bDropToGround, const Vector &vPos, const Vector &vMins, const Vector &vMaxs ) { if ( bDropToGround ) { trace_t trace; UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace ); return trace.endpos; } else { return vPos; } } //----------------------------------------------------------------------------- // Purpose: This function can be used to find a valid placement location for an entity. // Given an origin to start looking from and a minimum radius to place the entity at, // it will sweep out a circle around vOrigin and try to find a valid spot (on the ground) // where mins and maxs will fit. // Input : *pMainEnt - Entity to place // &vOrigin - Point to search around // fRadius - Radius to search within // nTries - Number of tries to attempt // &mins - mins of the Entity // &maxs - maxs of the Entity // &outPos - Return point // Output : Returns true and fills in outPos if it found a spot. //----------------------------------------------------------------------------- bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround ) { // This function moves the box out in each dimension in each step trying to find empty space like this: // // X // X X // Step 1: X Step 2: XXX Step 3: XXXXX // X X // X // Vector mins, maxs; pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs ); mins -= pMainEnt->GetAbsOrigin(); maxs -= pMainEnt->GetAbsOrigin(); // Put some padding on their bbox. float flPadSize = 5; Vector vTestMins = mins - Vector( flPadSize, flPadSize, flPadSize ); Vector vTestMaxs = maxs + Vector( flPadSize, flPadSize, flPadSize ); // First test the starting origin. if ( UTIL_IsSpaceEmpty( pMainEnt, vOrigin + vTestMins, vOrigin + vTestMaxs ) ) { outPos = MaybeDropToGround( pMainEnt, bDropToGround, vOrigin, vTestMins, vTestMaxs ); return true; } Vector vDims = vTestMaxs - vTestMins; // Keep branching out until we get too far. int iCurIteration = 0; int nMaxIterations = 15; int offset = 0; do { for ( int iDim=0; iDim < 3; iDim++ ) { float flCurOffset = offset * vDims[iDim]; for ( int iSign=0; iSign < 2; iSign++ ) { Vector vBase = vOrigin; vBase[iDim] += (iSign*2-1) * flCurOffset; if ( UTIL_IsSpaceEmpty( pMainEnt, vBase + vTestMins, vBase + vTestMaxs ) ) { // Ensure that there is a clear line of sight from the spawnpoint entity to the actual spawn point. // (Useful for keeping things from spawning behind walls near a spawn point) trace_t tr; UTIL_TraceLine( vOrigin, vBase, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0 ) { continue; } outPos = MaybeDropToGround( pMainEnt, bDropToGround, vBase, vTestMins, vTestMaxs ); return true; } } } ++offset; } while ( iCurIteration++ < nMaxIterations ); // Warning( "EntityPlacementTest for ent %d:%s failed!\n", pMainEnt->entindex(), pMainEnt->GetClassname() ); return false; } #else // GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::OnDataChanged( DataUpdateType_t updateType ) { BaseClass::OnDataChanged( updateType ); if ( State_Get() == GR_STATE_STARTGAME ) { m_iBirthdayMode = BIRTHDAY_RECALCULATE; } } void CTFGameRules::HandleOvertimeBegin() { C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pTFPlayer ) { pTFPlayer->EmitSound( "Game.Overtime" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFGameRules::ShouldShowTeamGoal( void ) { if ( State_Get() == GR_STATE_PREROUND || State_Get() == GR_STATE_RND_RUNNING || InSetup() ) return true; return false; } #endif #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::ShutdownCustomResponseRulesDicts() { DestroyCustomResponseSystems(); if ( m_ResponseRules.Count() != 0 ) { int nRuleCount = m_ResponseRules.Count(); for ( int iRule = 0; iRule < nRuleCount; ++iRule ) { m_ResponseRules[iRule].m_ResponseSystems.Purge(); } m_ResponseRules.Purge(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::InitCustomResponseRulesDicts() { MEM_ALLOC_CREDIT(); // Clear if necessary. ShutdownCustomResponseRulesDicts(); // Initialize the response rules for TF. m_ResponseRules.AddMultipleToTail( TF_CLASS_COUNT_ALL ); char szName[512]; for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_CLASS_COUNT_ALL; ++iClass ) { m_ResponseRules[iClass].m_ResponseSystems.AddMultipleToTail( MP_TF_CONCEPT_COUNT ); for ( int iConcept = 0; iConcept < MP_TF_CONCEPT_COUNT; ++iConcept ) { AI_CriteriaSet criteriaSet; criteriaSet.AppendCriteria( "playerclass", g_aPlayerClassNames_NonLocalized[iClass] ); criteriaSet.AppendCriteria( "Concept", g_pszMPConcepts[iConcept] ); // 1 point for player class and 1 point for concept. float flCriteriaScore = 2.0f; // Name. V_snprintf( szName, sizeof( szName ), "%s_%s\n", g_aPlayerClassNames_NonLocalized[iClass], g_pszMPConcepts[iConcept] ); m_ResponseRules[iClass].m_ResponseSystems[iConcept] = BuildCustomResponseSystemGivenCriteria( "scripts/talker/response_rules.txt", szName, criteriaSet, flCriteriaScore ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::SendHudNotification( IRecipientFilter &filter, HudNotification_t iType ) { UserMessageBegin( filter, "HudNotify" ); WRITE_BYTE( iType ); MessageEnd(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFGameRules::SendHudNotification( IRecipientFilter &filter, const char *pszText, const char *pszIcon, int iTeam /*= TEAM_UNASSIGNED*/ ) { UserMessageBegin( filter, "HudNotifyCustom" ); WRITE_STRING( pszText ); WRITE_STRING( pszIcon ); WRITE_BYTE( iTeam ); MessageEnd(); } //----------------------------------------------------------------------------- // Purpose: Is the player past the required delays for spawning //----------------------------------------------------------------------------- bool CTFGameRules::HasPassedMinRespawnTime( CBasePlayer *pPlayer ) { CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer ); if ( pTFPlayer && pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED ) return true; float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer ); return ( gpGlobals->curtime > flMinSpawnTime ); } #endif #ifdef CLIENT_DLL const char *CTFGameRules::GetVideoFileForMap( bool bWithExtension /*= true*/ ) { char mapname[MAX_MAP_NAME]; Q_FileBase( engine->GetLevelName(), mapname, sizeof( mapname ) ); Q_strlower( mapname ); #ifdef _X360 // need to remove the .360 extension on the end of the map name char *pExt = Q_stristr( mapname, ".360" ); if ( pExt ) { *pExt = '\0'; } #endif static char strFullpath[MAX_PATH]; Q_strncpy( strFullpath, "media/", MAX_PATH ); // Assume we must play out of the media directory Q_strncat( strFullpath, mapname, MAX_PATH ); if ( bWithExtension ) { Q_strncat( strFullpath, ".bik", MAX_PATH ); // Assume we're a .bik extension type } return strFullpath; } #endif