//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "keyvalues.h" #include "gamerules.h" #include "teamplay_gamerules.h" #ifdef CLIENT_DLL #include "c_baseplayer.h" #include "c_team.h" #else #include "player.h" #include "game.h" #include "gamevars_shared.h" #include "team.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef GAME_DLL static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; static int team_scores[MAX_TEAMS]; static int num_teams = 0; extern bool g_fGameOver; extern ConVar sv_allchat; extern ConVar spec_replay_bot; // [jason] Used to allow dead chat as well extern ConVar sv_deadtalk; REGISTER_GAMERULES_CLASS( CTeamplayRules ); CTeamplayRules::CTeamplayRules() { m_DisableDeathMessages = false; m_DisableDeathPenalty = false; m_bSwitchTeams = false; m_bScrambleTeams = false; memset( team_names, 0, sizeof(team_names) ); memset( team_scores, 0, sizeof(team_scores) ); num_teams = 0; // Copy over the team from the server config m_szTeamList[0] = 0; RecountTeams(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTeamplayRules::Precache( void ) { BaseClass::Precache(); // Call the Team Manager's precaches for ( int i = 0; i < GetNumberOfTeams(); i++ ) { CTeam *pTeam = GetGlobalTeam( i ); pTeam->Precache(); } } //----------------------------------------------------------------------------- // Purpose: // // WARNING - this function is NOT called in CS:GO // // CCSGameRules (which has CTeamplayRules as a baseclass) ::Think() calls // CGameRules::Think() directly, bypassing CTeamplayRules::Think() and // CMultiplayRules::Think() // // Code placed in here is likely to NEVER be called //----------------------------------------------------------------------------- void CTeamplayRules::Think ( void ) { BaseClass::Think(); ///// Check game rules ///// if ( g_fGameOver ) // someone else quit the game already { BaseClass::Think(); return; } float flTimeLimit = mp_timelimit.GetFloat() * 60; if ( flTimeLimit != 0 && gpGlobals->curtime >= flTimeLimit ) { ChangeLevel(); return; } float flFragLimit = fraglimit.GetFloat(); if ( flFragLimit ) { // check if any team is over the frag limit for ( int i = 0; i < num_teams; i++ ) { if ( team_scores[i] >= flFragLimit ) { ChangeLevel(); return; } } } } //========================================================= // ClientCommand // the user has typed a command which is unrecognized by everything else; // this check to see if the gamerules knows anything about the command //========================================================= bool CTeamplayRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) { if( BaseClass::ClientCommand( pEdict, args ) ) return true; if ( FStrEq( args[0], "menuselect" ) ) { if ( args.ArgC() < 2 ) return true; //int slot = atoi( args[1] ); // select the item from the current menu return true; } return false; } const char *CTeamplayRules::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return "default"; } //========================================================= // InitHUD //========================================================= void CTeamplayRules::InitHUD( CBasePlayer *pPlayer ) { SetDefaultPlayerTeam( pPlayer ); BaseClass::InitHUD( pPlayer ); // this does a string compare and doesn't need to happen every frame //RecountTeams(); /* TODO this has to be rewritten, maybe add a new USERINFO cvar "team" const char *team = engine->GetClientConVarValue( pPlayer->entindex(), "cl_team" ); // update the current player of the team he is joining char text[1024]; if ( !strcmp( mdls, pPlayer->TeamName() ) ) { Q_snprintf( text,sizeof(text), "You are on team \'%s\'\n", pPlayer->TeamName() ); } else { Q_snprintf( text,sizeof(text), "You were assigned to team %s\n", pPlayer->TeamName() ); } ChangePlayerTeam( pPlayer, pPlayer->TeamName(), false, false ); if ( Q_strlen( pPlayer->TeamName() ) > 0 ) { UTIL_SayText( text, pPlayer ); } RecountTeams(); */ } void CTeamplayRules::ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, bool bKill, bool bGib ) { int damageFlags = DMG_GENERIC; // int clientIndex = pPlayer->entindex(); if ( !bGib ) { damageFlags |= DMG_NEVERGIB; } else { damageFlags |= DMG_ALWAYSGIB; } // copy out the team name from the model // pPlayer->SetTeamName( pTeamName ); } //----------------------------------------------------------------------------- // Purpose: Player has just left the game //----------------------------------------------------------------------------- void CTeamplayRules::ClientDisconnected( edict_t *pClient ) { // Msg( "CLIENT DISCONNECTED, REMOVING FROM TEAM.\n" ); CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); if ( pPlayer ) { pPlayer->SetConnected( PlayerDisconnecting ); // Remove the player from his team if ( pPlayer->GetTeam() ) { pPlayer->ChangeTeam( 0 ); } } BaseClass::ClientDisconnected( pClient ); } //========================================================= // ClientUserInfoChanged //========================================================= void CTeamplayRules::ClientSettingsChanged( CBasePlayer *pPlayer ) { /* TODO: handle skin, model & team changes char text[1024]; // skin/color/model changes int iTeam = Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_team" ) ); int iClass = Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_class" ) ); if ( defaultteam.GetBool() ) { // int clientIndex = pPlayer->entindex(); // engine->SetClientKeyValue( clientIndex, "model", pPlayer->TeamName() ); // engine->SetClientKeyValue( clientIndex, "team", pPlayer->TeamName() ); UTIL_SayText( "Not allowed to change teams in this game!\n", pPlayer ); return; } if ( defaultteam.GetFloat() || !IsValidTeam( mdls ) ) { // int clientIndex = pPlayer->entindex(); // engine->SetClientKeyValue( clientIndex, "model", pPlayer->TeamName() ); Q_snprintf( text,sizeof(text), "Can't change team to \'%s\'\n", mdls ); UTIL_SayText( text, pPlayer ); Q_snprintf( text,sizeof(text), "Server limits teams to \'%s\'\n", m_szTeamList ); UTIL_SayText( text, pPlayer ); return; } ChangePlayerTeam( pPlayer, mdls, true, true ); // recound stuff RecountTeams(); */ const char *pszName = engine->GetClientConVarValue( pPlayer->entindex(), "name" ); const char *pszOldName = pPlayer->GetPlayerName(); // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) // Note, this is case sensitive if ( ( Q_strcmp( pszOldName, pszName ) != 0 ) && CanClientCustomizeOwnIdentity() ) { if ( pszOldName[0] != '\0' ) { IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" ); if ( event ) { event->SetInt( "userid", pPlayer->GetUserID() ); event->SetString( "oldname", pszOldName ); event->SetString( "newname", pszName ); gameeventmanager->FireEvent( event ); } } pPlayer->SetPlayerName( pszName ); } } //========================================================= // Deathnotice. //========================================================= void CTeamplayRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { if ( m_DisableDeathMessages ) return; CBaseEntity *pKiller = info.GetAttacker(); if ( pVictim && pKiller && pKiller->IsPlayer() ) { CBasePlayer *pk = (CBasePlayer*)pKiller; if ( pk ) { if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) { IGameEvent * event = gameeventmanager->CreateEvent( "player_death" ); if ( event ) { event->SetInt("killer", pk->GetUserID() ); event->SetInt("victim", pVictim->GetUserID() ); event->SetInt("priority", 7 ); // HLTV event priority, not transmitted if( !OnReplayPrompt( pVictim, pk ) ) event->SetBool( "noreplay", true ); gameeventmanager->FireEvent( event ); } return; } } } BaseClass::DeathNotice( pVictim, info ); } //========================================================= //========================================================= void CTeamplayRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { if ( !m_DisableDeathPenalty ) { BaseClass::PlayerKilled( pVictim, info ); RecountTeams(); } } //========================================================= // IsTeamplay //========================================================= bool CTeamplayRules::IsTeamplay( void ) { return true; } bool CTeamplayRules::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) { if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) { // my teammate hit me. if ( ( mp_friendlyfire.GetInt() == 0 ) && ( pAttacker != pPlayer ) ) { // friendly fire is off, and this hit came from someone other than myself, then don't get hurt return false; } } return BaseClass::FPlayerCanTakeDamage( pPlayer, pAttacker ); } //========================================================= //========================================================= int CTeamplayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) { // half life multiplay has a simple concept of Player Relationships. // you are either on another player's team, or you are not. if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) return GR_NOTTEAMMATE; // don't do string compares, just compare the team number // if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) // { // return GR_TEAMMATE; // } if ( pPlayer->GetTeamNumber() != TEAM_INVALID && pTarget->GetTeamNumber() != TEAM_INVALID && pPlayer->GetTeamNumber() == pTarget->GetTeamNumber() ) { return GR_TEAMMATE; } return GR_NOTTEAMMATE; } //----------------------------------------------------------------------------- // Purpose: // Input : *pListener - // *pSpeaker - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTeamplayRules::PlayerCanHearChat( CBasePlayer *pListener, CBasePlayer *pSpeaker, bool bTeamOnly ) { if ( !bTeamOnly && sv_allchat.GetBool() ) { if ( !pSpeaker->IsAlive() ) { // [jason] convar allows the dead to speak/chat with the living return ( ( !pListener->IsAlive() || sv_deadtalk.GetBool() ) && ( PlayerRelationship( pListener, pSpeaker ) == GR_TEAMMATE ) ); } } return ( PlayerRelationship( pListener, pSpeaker ) == GR_TEAMMATE ); } //========================================================= //========================================================= bool CTeamplayRules::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) { // always autoaim, unless target is a teammate CBaseEntity *pTgt = CBaseEntity::Instance( target ); if ( pTgt && pTgt->IsPlayer() ) { if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) return false; // don't autoaim at teammates } return BaseClass::ShouldAutoAim( pPlayer, target ); } //========================================================= //========================================================= int CTeamplayRules::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) { if ( !pKilled ) return 0; if ( !pAttacker ) return 1; if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) return -1; return 1; } //========================================================= //========================================================= // const char *CTeamplayRules::GetTeamID( CBaseEntity *pEntity ) // { // if ( pEntity == NULL || pEntity->edict() == NULL ) // return ""; // // // return their team name // return pEntity->TeamID(); // } int CTeamplayRules::GetTeamIndex( const char *pTeamName ) { if ( pTeamName && *pTeamName != 0 ) { // try to find existing team for ( int tm = 0; tm < num_teams; tm++ ) { if ( !stricmp( team_names[tm], pTeamName ) ) return tm; } } return -1; // No match } const char *CTeamplayRules::GetIndexedTeamName( int teamIndex ) { if ( teamIndex < 0 || teamIndex >= num_teams ) return ""; return team_names[ teamIndex ]; } bool CTeamplayRules::IsValidTeam( const char *pTeamName ) { if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set return true; return ( GetTeamIndex( pTeamName ) != -1 ) ? true : false; } const char *CTeamplayRules::TeamWithFewestPlayers( void ) { int i; int minPlayers = MAX_TEAMS; int teamCount[ MAX_TEAMS ]; char *pTeamName = NULL; memset( teamCount, 0, MAX_TEAMS * sizeof(int) ); // loop through all clients, count number of players on each team for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *plr = UTIL_PlayerByIndex( i ); if ( plr ) { int team = GetTeamIndex( plr->TeamID() ); if ( team >= 0 ) teamCount[team] ++; } } // Find team with least players for ( i = 0; i < num_teams; i++ ) { if ( teamCount[i] < minPlayers ) { minPlayers = teamCount[i]; pTeamName = team_names[i]; } } return pTeamName; } //========================================================= //========================================================= void CTeamplayRules::RecountTeams( void ) { char *pName; char teamlist[TEAMPLAY_TEAMLISTLENGTH]; // loop through all teams, recounting everything num_teams = 0; // Copy all of the teams from the teamlist // make a copy because strtok is destructive Q_strncpy( teamlist, m_szTeamList, sizeof(teamlist) ); pName = teamlist; pName = strtok( pName, ";" ); while ( pName != NULL && *pName ) { if ( GetTeamIndex( pName ) < 0 ) { Q_strncpy( team_names[num_teams], pName, sizeof(team_names[num_teams])); num_teams++; } pName = strtok( NULL, ";" ); } if ( num_teams < 2 ) { num_teams = 0; m_teamLimit = false; } // Sanity check memset( team_scores, 0, sizeof(team_scores) ); // loop through all clients for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *plr = UTIL_PlayerByIndex( i ); if ( plr ) { const char *pTeamName = plr->TeamID(); // try add to existing team int tm = GetTeamIndex( pTeamName ); if ( tm < 0 ) // no team match found { if ( !m_teamLimit ) { // add to new team tm = num_teams; num_teams++; team_scores[tm] = 0; Q_strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); } } if ( tm >= 0 ) { team_scores[tm] += plr->FragCount(); } } } } #endif // GAME_DLL