2001-05-06 20:50:27 +00:00
// Copyright (C) 1999-2000 Id Software, Inc.
//
# include "g_local.h"
2001-06-08 04:47:30 +00:00
# define RQ3_NONAMEPLAYER "Nameless"
2001-05-06 20:50:27 +00:00
// g_client.c -- client functions that don't happen every frame
static vec3_t playerMins = { - 15 , - 15 , - 24 } ;
static vec3_t playerMaxs = { 15 , 15 , 32 } ;
/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
potential spawning position for deathmatch games .
The first time a player enters the game , they will be at an ' initial ' spot .
Targets will be fired when someone spawns in on them .
" nobots " will prevent bots from using this spot .
" nohumans " will prevent non - bots from using this spot .
*/
void SP_info_player_deathmatch ( gentity_t * ent ) {
int i ;
G_SpawnInt ( " nobots " , " 0 " , & i ) ;
if ( i ) {
ent - > flags | = FL_NO_BOTS ;
}
G_SpawnInt ( " nohumans " , " 0 " , & i ) ;
if ( i ) {
ent - > flags | = FL_NO_HUMANS ;
}
}
/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
equivelant to info_player_deathmatch
*/
void SP_info_player_start ( gentity_t * ent ) {
ent - > classname = " info_player_deathmatch " ;
SP_info_player_deathmatch ( ent ) ;
}
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
The intermission will be viewed from this point . Target an info_notnull for the view direction .
*/
void SP_info_player_intermission ( gentity_t * ent ) {
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
SelectSpawnPoint
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = = =
SpotWouldTelefrag
= = = = = = = = = = = = = = = =
*/
qboolean SpotWouldTelefrag ( gentity_t * spot ) {
int i , num ;
int touch [ MAX_GENTITIES ] ;
gentity_t * hit ;
vec3_t mins , maxs ;
VectorAdd ( spot - > s . origin , playerMins , mins ) ;
VectorAdd ( spot - > s . origin , playerMaxs , maxs ) ;
num = trap_EntitiesInBox ( mins , maxs , touch , MAX_GENTITIES ) ;
for ( i = 0 ; i < num ; i + + ) {
hit = & g_entities [ touch [ i ] ] ;
//if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
if ( hit - > client ) {
return qtrue ;
}
}
return qfalse ;
}
/*
= = = = = = = = = = = = = = = =
SelectNearestDeathmatchSpawnPoint
Find the spot that we DON ' T want to use
= = = = = = = = = = = = = = = =
*/
# define MAX_SPAWN_POINTS 128
gentity_t * SelectNearestDeathmatchSpawnPoint ( vec3_t from ) {
gentity_t * spot ;
vec3_t delta ;
float dist , nearestDist ;
gentity_t * nearestSpot ;
nearestDist = 999999 ;
nearestSpot = NULL ;
spot = NULL ;
while ( ( spot = G_Find ( spot , FOFS ( classname ) , " info_player_deathmatch " ) ) ! = NULL ) {
VectorSubtract ( spot - > s . origin , from , delta ) ;
dist = VectorLength ( delta ) ;
if ( dist < nearestDist ) {
nearestDist = dist ;
nearestSpot = spot ;
}
}
return nearestSpot ;
}
/*
= = = = = = = = = = = = = = = =
SelectRandomDeathmatchSpawnPoint
go to a random point that doesn ' t telefrag
= = = = = = = = = = = = = = = =
*/
# define MAX_SPAWN_POINTS 128
gentity_t * SelectRandomDeathmatchSpawnPoint ( void ) {
gentity_t * spot ;
int count ;
int selection ;
gentity_t * spots [ MAX_SPAWN_POINTS ] ;
count = 0 ;
spot = NULL ;
while ( ( spot = G_Find ( spot , FOFS ( classname ) , " info_player_deathmatch " ) ) ! = NULL ) {
if ( SpotWouldTelefrag ( spot ) ) {
continue ;
}
spots [ count ] = spot ;
count + + ;
}
if ( ! count ) { // no spots that won't telefrag
return G_Find ( NULL , FOFS ( classname ) , " info_player_deathmatch " ) ;
}
selection = rand ( ) % count ;
return spots [ selection ] ;
}
/*
= = = = = = = = = = =
SelectRandomFurthestSpawnPoint
Chooses a player start , deathmatch start , etc
= = = = = = = = = = = =
*/
gentity_t * SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint , vec3_t origin , vec3_t angles ) {
gentity_t * spot ;
vec3_t delta ;
float dist ;
float list_dist [ 64 ] ;
gentity_t * list_spot [ 64 ] ;
int numSpots , rnd , i , j ;
numSpots = 0 ;
spot = NULL ;
while ( ( spot = G_Find ( spot , FOFS ( classname ) , " info_player_deathmatch " ) ) ! = NULL ) {
if ( SpotWouldTelefrag ( spot ) ) {
continue ;
}
VectorSubtract ( spot - > s . origin , avoidPoint , delta ) ;
dist = VectorLength ( delta ) ;
for ( i = 0 ; i < numSpots ; i + + ) {
if ( dist > list_dist [ i ] ) {
if ( numSpots > = 64 )
numSpots = 64 - 1 ;
for ( j = numSpots ; j > i ; j - - ) {
list_dist [ j ] = list_dist [ j - 1 ] ;
list_spot [ j ] = list_spot [ j - 1 ] ;
}
list_dist [ i ] = dist ;
list_spot [ i ] = spot ;
numSpots + + ;
if ( numSpots > 64 )
numSpots = 64 ;
break ;
}
}
if ( i > = numSpots & & numSpots < 64 ) {
list_dist [ numSpots ] = dist ;
list_spot [ numSpots ] = spot ;
numSpots + + ;
}
}
if ( ! numSpots ) {
spot = G_Find ( NULL , FOFS ( classname ) , " info_player_deathmatch " ) ;
if ( ! spot )
G_Error ( " Couldn't find a spawn point " ) ;
VectorCopy ( spot - > s . origin , origin ) ;
origin [ 2 ] + = 9 ;
VectorCopy ( spot - > s . angles , angles ) ;
return spot ;
}
// select a random spot from the spawn points furthest away
rnd = random ( ) * ( numSpots / 2 ) ;
VectorCopy ( list_spot [ rnd ] - > s . origin , origin ) ;
origin [ 2 ] + = 9 ;
VectorCopy ( list_spot [ rnd ] - > s . angles , angles ) ;
return list_spot [ rnd ] ;
}
/*
= = = = = = = = = = =
SelectSpawnPoint
Chooses a player start , deathmatch start , etc
= = = = = = = = = = = =
*/
gentity_t * SelectSpawnPoint ( vec3_t avoidPoint , vec3_t origin , vec3_t angles ) {
return SelectRandomFurthestSpawnPoint ( avoidPoint , origin , angles ) ;
/*
gentity_t * spot ;
gentity_t * nearestSpot ;
nearestSpot = SelectNearestDeathmatchSpawnPoint ( avoidPoint ) ;
spot = SelectRandomDeathmatchSpawnPoint ( ) ;
if ( spot = = nearestSpot ) {
// roll again if it would be real close to point of death
spot = SelectRandomDeathmatchSpawnPoint ( ) ;
if ( spot = = nearestSpot ) {
// last try
spot = SelectRandomDeathmatchSpawnPoint ( ) ;
}
}
// find a single player start spot
if ( ! spot ) {
G_Error ( " Couldn't find a spawn point " ) ;
}
VectorCopy ( spot - > s . origin , origin ) ;
origin [ 2 ] + = 9 ;
VectorCopy ( spot - > s . angles , angles ) ;
return spot ;
*/
}
/*
= = = = = = = = = = =
SelectInitialSpawnPoint
Try to find a spawn point marked ' initial ' , otherwise
use normal spawn selection .
= = = = = = = = = = = =
*/
gentity_t * SelectInitialSpawnPoint ( vec3_t origin , vec3_t angles ) {
gentity_t * spot ;
spot = NULL ;
while ( ( spot = G_Find ( spot , FOFS ( classname ) , " info_player_deathmatch " ) ) ! = NULL ) {
if ( spot - > spawnflags & 1 ) {
break ;
}
}
if ( ! spot | | SpotWouldTelefrag ( spot ) ) {
return SelectSpawnPoint ( vec3_origin , origin , angles ) ;
}
VectorCopy ( spot - > s . origin , origin ) ;
origin [ 2 ] + = 9 ;
VectorCopy ( spot - > s . angles , angles ) ;
return spot ;
}
/*
= = = = = = = = = = =
SelectSpectatorSpawnPoint
= = = = = = = = = = = =
*/
gentity_t * SelectSpectatorSpawnPoint ( vec3_t origin , vec3_t angles ) {
FindIntermissionPoint ( ) ;
VectorCopy ( level . intermission_origin , origin ) ;
VectorCopy ( level . intermission_angle , angles ) ;
return NULL ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
BODYQUE
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = =
InitBodyQue
= = = = = = = = = = = = = = =
*/
void InitBodyQue ( void ) {
int i ;
gentity_t * ent ;
level . bodyQueIndex = 0 ;
for ( i = 0 ; i < BODY_QUEUE_SIZE ; i + + ) {
ent = G_Spawn ( ) ;
ent - > classname = " bodyque " ;
ent - > neverFree = qtrue ;
level . bodyQue [ i ] = ent ;
}
}
/*
= = = = = = = = = = = = =
BodySink
After sitting around for five seconds , fall into the ground and dissapear
= = = = = = = = = = = = =
*/
void BodySink ( gentity_t * ent ) {
if ( level . time - ent - > timestamp > 6500 ) {
// the body ques are never actually freed, they are just unlinked
trap_UnlinkEntity ( ent ) ;
ent - > physicsObject = qfalse ;
return ;
}
ent - > nextthink = level . time + 100 ;
ent - > s . pos . trBase [ 2 ] - = 1 ;
}
/*
= = = = = = = = = = = = =
CopyToBodyQue
A player is respawning , so make an entity that looks
just like the existing corpse to leave behind .
= = = = = = = = = = = = =
*/
void CopyToBodyQue ( gentity_t * ent ) {
# ifdef MISSIONPACK
gentity_t * e ;
int i ;
# endif
gentity_t * body ;
int contents ;
trap_UnlinkEntity ( ent ) ;
// if client is in a nodrop area, don't leave the body
contents = trap_PointContents ( ent - > s . origin , - 1 ) ;
if ( contents & CONTENTS_NODROP ) {
return ;
}
// grab a body que and cycle to the next one
body = level . bodyQue [ level . bodyQueIndex ] ;
level . bodyQueIndex = ( level . bodyQueIndex + 1 ) % BODY_QUEUE_SIZE ;
trap_UnlinkEntity ( body ) ;
body - > s = ent - > s ;
body - > s . eFlags = EF_DEAD ; // clear EF_TALK, etc
# ifdef MISSIONPACK
if ( ent - > s . eFlags & EF_KAMIKAZE ) {
body - > s . eFlags | = EF_KAMIKAZE ;
// check if there is a kamikaze timer around for this owner
for ( i = 0 ; i < MAX_GENTITIES ; i + + ) {
e = & g_entities [ i ] ;
if ( ! e - > inuse )
continue ;
if ( e - > activator ! = ent )
continue ;
if ( strcmp ( e - > classname , " kamikaze timer " ) )
continue ;
e - > activator = body ;
break ;
}
}
# endif
body - > s . powerups = 0 ; // clear powerups
body - > s . loopSound = 0 ; // clear lava burning
body - > s . number = body - g_entities ;
body - > timestamp = level . time ;
body - > physicsObject = qtrue ;
body - > physicsBounce = 0 ; // don't bounce
if ( body - > s . groundEntityNum = = ENTITYNUM_NONE ) {
body - > s . pos . trType = TR_GRAVITY ;
body - > s . pos . trTime = level . time ;
VectorCopy ( ent - > client - > ps . velocity , body - > s . pos . trDelta ) ;
} else {
body - > s . pos . trType = TR_STATIONARY ;
}
body - > s . event = 0 ;
// change the animation to the last-frame only, so the sequence
// doesn't repeat anew for the body
switch ( body - > s . legsAnim & ~ ANIM_TOGGLEBIT ) {
case BOTH_DEATH1 :
case BOTH_DEAD1 :
body - > s . torsoAnim = body - > s . legsAnim = BOTH_DEAD1 ;
break ;
case BOTH_DEATH2 :
case BOTH_DEAD2 :
body - > s . torsoAnim = body - > s . legsAnim = BOTH_DEAD2 ;
break ;
case BOTH_DEATH3 :
case BOTH_DEAD3 :
default :
body - > s . torsoAnim = body - > s . legsAnim = BOTH_DEAD3 ;
break ;
}
body - > r . svFlags = ent - > r . svFlags ;
VectorCopy ( ent - > r . mins , body - > r . mins ) ;
VectorCopy ( ent - > r . maxs , body - > r . maxs ) ;
VectorCopy ( ent - > r . absmin , body - > r . absmin ) ;
VectorCopy ( ent - > r . absmax , body - > r . absmax ) ;
body - > clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP ;
body - > r . contents = CONTENTS_CORPSE ;
body - > r . ownerNum = ent - > s . number ;
body - > nextthink = level . time + 5000 ;
body - > think = BodySink ;
body - > die = body_die ;
// don't take more damage if already gibbed
if ( ent - > health < = GIB_HEALTH ) {
body - > takedamage = qfalse ;
} else {
body - > takedamage = qtrue ;
}
VectorCopy ( body - > s . pos . trBase , body - > r . currentOrigin ) ;
trap_LinkEntity ( body ) ;
}
//======================================================================
/*
= = = = = = = = = = = = = = = = = =
SetClientViewAngle
= = = = = = = = = = = = = = = = = =
*/
void SetClientViewAngle ( gentity_t * ent , vec3_t angle ) {
int i ;
// set the delta angle
for ( i = 0 ; i < 3 ; i + + ) {
int cmdAngle ;
cmdAngle = ANGLE2SHORT ( angle [ i ] ) ;
ent - > client - > ps . delta_angles [ i ] = cmdAngle - ent - > client - > pers . cmd . angles [ i ] ;
}
VectorCopy ( angle , ent - > s . angles ) ;
VectorCopy ( ent - > s . angles , ent - > client - > ps . viewangles ) ;
}
/*
= = = = = = = = = = = = = = = =
respawn
= = = = = = = = = = = = = = = =
*/
void respawn ( gentity_t * ent ) {
2001-06-08 04:47:30 +00:00
//gentity_t *tent;
2001-05-06 20:50:27 +00:00
CopyToBodyQue ( ent ) ;
ClientSpawn ( ent ) ;
// add a teleportation effect
2001-06-08 04:47:30 +00:00
//Elder: removed
//tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
//tent->s.clientNum = ent->s.clientNum;
2001-05-06 20:50:27 +00:00
}
/*
= = = = = = = = = = = = = = = =
TeamCount
Returns number of players on a team
= = = = = = = = = = = = = = = =
*/
team_t TeamCount ( int ignoreClientNum , int team ) {
int i ;
int count = 0 ;
for ( i = 0 ; i < level . maxclients ; i + + ) {
if ( i = = ignoreClientNum ) {
continue ;
}
if ( level . clients [ i ] . pers . connected = = CON_DISCONNECTED ) {
continue ;
}
if ( level . clients [ i ] . sess . sessionTeam = = team ) {
count + + ;
}
}
return count ;
}
/*
= = = = = = = = = = = = = = = =
TeamLeader
Returns the client number of the team leader
= = = = = = = = = = = = = = = =
*/
int TeamLeader ( int team ) {
int i ;
for ( i = 0 ; i < level . maxclients ; i + + ) {
if ( level . clients [ i ] . pers . connected = = CON_DISCONNECTED ) {
continue ;
}
if ( level . clients [ i ] . sess . sessionTeam = = team ) {
if ( level . clients [ i ] . sess . teamLeader )
return i ;
}
}
return - 1 ;
}
/*
= = = = = = = = = = = = = = = =
PickTeam
= = = = = = = = = = = = = = = =
*/
team_t PickTeam ( int ignoreClientNum ) {
int counts [ TEAM_NUM_TEAMS ] ;
counts [ TEAM_BLUE ] = TeamCount ( ignoreClientNum , TEAM_BLUE ) ;
counts [ TEAM_RED ] = TeamCount ( ignoreClientNum , TEAM_RED ) ;
if ( counts [ TEAM_BLUE ] > counts [ TEAM_RED ] ) {
return TEAM_RED ;
}
if ( counts [ TEAM_RED ] > counts [ TEAM_BLUE ] ) {
return TEAM_BLUE ;
}
// equal team count, so join the team with the lowest score
if ( level . teamScores [ TEAM_BLUE ] > level . teamScores [ TEAM_RED ] ) {
return TEAM_RED ;
}
return TEAM_BLUE ;
}
/*
= = = = = = = = = = =
ForceClientSkin
Forces a client ' s skin ( for teamplay )
= = = = = = = = = = =
*/
2001-08-01 19:52:17 +00:00
/*
2001-05-06 20:50:27 +00:00
static void ForceClientSkin ( gclient_t * client , char * model , const char * skin ) {
char * p ;
if ( ( p = Q_strrchr ( model , ' / ' ) ) ! = 0 ) {
* p = 0 ;
}
Q_strcat ( model , MAX_QPATH , " / " ) ;
Q_strcat ( model , MAX_QPATH , skin ) ;
}
2001-08-01 19:52:17 +00:00
*/
2001-05-06 20:50:27 +00:00
/*
= = = = = = = = = = =
ClientCheckName
= = = = = = = = = = = =
*/
static void ClientCleanName ( const char * in , char * out , int outSize ) {
int len , colorlessLen ;
char ch ;
char * p ;
int spaces ;
//save room for trailing null byte
outSize - - ;
len = 0 ;
colorlessLen = 0 ;
p = out ;
* p = 0 ;
spaces = 0 ;
while ( 1 ) {
ch = * in + + ;
if ( ! ch ) {
break ;
}
// don't allow leading spaces
if ( ! * p & & ch = = ' ' ) {
continue ;
}
// check colors
if ( ch = = Q_COLOR_ESCAPE ) {
// solo trailing carat is not a color prefix
if ( ! * in ) {
break ;
}
// don't allow black in a name, period
if ( ColorIndex ( * in ) = = 0 ) {
in + + ;
continue ;
}
// make sure room in dest for both chars
if ( len > outSize - 2 ) {
break ;
}
* out + + = ch ;
* out + + = * in + + ;
len + = 2 ;
continue ;
}
// don't allow too many consecutive spaces
if ( ch = = ' ' ) {
spaces + + ;
if ( spaces > 3 ) {
continue ;
}
}
else {
spaces = 0 ;
}
if ( len > outSize - 1 ) {
break ;
}
* out + + = ch ;
colorlessLen + + ;
len + + ;
}
* out = 0 ;
// don't allow empty names
if ( * p = = 0 | | colorlessLen = = 0 ) {
2001-06-08 04:47:30 +00:00
//Elder: change to what we want
Q_strncpyz ( p , RQ3_NONAMEPLAYER , outSize ) ;
2001-05-06 20:50:27 +00:00
}
}
/*
= = = = = = = = = = =
ClientUserInfoChanged
Called from ClientConnect when the player first connects and
directly by the server system when the player updates a userinfo variable .
The game can override any of the settings and call trap_SetUserinfo
if desired .
= = = = = = = = = = = =
*/
void ClientUserinfoChanged ( int clientNum ) {
gentity_t * ent ;
int teamTask , teamLeader , team , health ;
char * s ;
char model [ MAX_QPATH ] ;
char headModel [ MAX_QPATH ] ;
char oldname [ MAX_STRING_CHARS ] ;
gclient_t * client ;
char c1 [ MAX_INFO_STRING ] ;
2001-08-01 19:52:17 +00:00
char c2 [ MAX_INFO_STRING ] ;
2001-05-06 20:50:27 +00:00
char redTeam [ MAX_INFO_STRING ] ;
char blueTeam [ MAX_INFO_STRING ] ;
char userinfo [ MAX_INFO_STRING ] ;
ent = g_entities + clientNum ;
client = ent - > client ;
trap_GetUserinfo ( clientNum , userinfo , sizeof ( userinfo ) ) ;
// check for malformed or illegal info strings
if ( ! Info_Validate ( userinfo ) ) {
strcpy ( userinfo , " \\ name \\ badinfo " ) ;
}
// check for local client
s = Info_ValueForKey ( userinfo , " ip " ) ;
if ( ! strcmp ( s , " localhost " ) ) {
client - > pers . localClient = qtrue ;
}
// check the item prediction
s = Info_ValueForKey ( userinfo , " cg_predictItems " ) ;
if ( ! atoi ( s ) ) {
client - > pers . predictItemPickup = qfalse ;
} else {
client - > pers . predictItemPickup = qtrue ;
}
// set name
Q_strncpyz ( oldname , client - > pers . netname , sizeof ( oldname ) ) ;
s = Info_ValueForKey ( userinfo , " name " ) ;
ClientCleanName ( s , client - > pers . netname , sizeof ( client - > pers . netname ) ) ;
if ( client - > sess . sessionTeam = = TEAM_SPECTATOR ) {
if ( client - > sess . spectatorState = = SPECTATOR_SCOREBOARD ) {
Q_strncpyz ( client - > pers . netname , " scoreboard " , sizeof ( client - > pers . netname ) ) ;
}
}
if ( client - > pers . connected = = CON_CONNECTED ) {
if ( strcmp ( oldname , client - > pers . netname ) ) {
trap_SendServerCommand ( - 1 , va ( " print \" %s " S_COLOR_WHITE " renamed to %s \n \" " , oldname ,
client - > pers . netname ) ) ;
}
}
// set max health
# ifdef MISSIONPACK
if ( client - > ps . powerups [ PW_GUARD ] ) {
client - > pers . maxHealth = 200 ;
} else {
health = atoi ( Info_ValueForKey ( userinfo , " handicap " ) ) ;
client - > pers . maxHealth = health ;
if ( client - > pers . maxHealth < 1 | | client - > pers . maxHealth > 100 ) {
client - > pers . maxHealth = 100 ;
}
}
# else
health = atoi ( Info_ValueForKey ( userinfo , " handicap " ) ) ;
client - > pers . maxHealth = health ;
if ( client - > pers . maxHealth < 1 | | client - > pers . maxHealth > 100 ) {
client - > pers . maxHealth = 100 ;
}
# endif
//client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
// set model
if ( g_gametype . integer > = GT_TEAM ) {
Q_strncpyz ( model , Info_ValueForKey ( userinfo , " team_model " ) , sizeof ( model ) ) ;
Q_strncpyz ( headModel , Info_ValueForKey ( userinfo , " team_headmodel " ) , sizeof ( headModel ) ) ;
} else {
Q_strncpyz ( model , Info_ValueForKey ( userinfo , " model " ) , sizeof ( model ) ) ;
Q_strncpyz ( headModel , Info_ValueForKey ( userinfo , " headmodel " ) , sizeof ( headModel ) ) ;
}
// bots set their team a few frames later
if ( g_gametype . integer > = GT_TEAM & & g_entities [ clientNum ] . r . svFlags & SVF_BOT ) {
s = Info_ValueForKey ( userinfo , " team " ) ;
if ( ! Q_stricmp ( s , " red " ) | | ! Q_stricmp ( s , " r " ) ) {
team = TEAM_RED ;
} else if ( ! Q_stricmp ( s , " blue " ) | | ! Q_stricmp ( s , " b " ) ) {
team = TEAM_BLUE ;
} else {
// pick the team with the least number of players
team = PickTeam ( clientNum ) ;
}
}
else {
team = client - > sess . sessionTeam ;
}
2001-08-01 19:52:17 +00:00
/* NOTE: all client side now
2001-05-06 20:50:27 +00:00
// team
switch ( team ) {
case TEAM_RED :
ForceClientSkin ( client , model , " red " ) ;
2001-08-01 19:52:17 +00:00
// ForceClientSkin(client, headModel, "red");
2001-05-06 20:50:27 +00:00
break ;
case TEAM_BLUE :
ForceClientSkin ( client , model , " blue " ) ;
2001-08-01 19:52:17 +00:00
// ForceClientSkin(client, headModel, "blue");
2001-05-06 20:50:27 +00:00
break ;
}
// don't ever use a default skin in teamplay, it would just waste memory
// however bots will always join a team but they spawn in as spectator
if ( g_gametype . integer > = GT_TEAM & & team = = TEAM_SPECTATOR ) {
ForceClientSkin ( client , model , " red " ) ;
2001-08-01 19:52:17 +00:00
// ForceClientSkin(client, headModel, "red");
2001-05-06 20:50:27 +00:00
}
2001-08-01 19:52:17 +00:00
*/
2001-05-06 20:50:27 +00:00
# ifdef MISSIONPACK
if ( g_gametype . integer > = GT_TEAM ) {
client - > pers . teamInfo = qtrue ;
} else {
s = Info_ValueForKey ( userinfo , " teamoverlay " ) ;
if ( ! * s | | atoi ( s ) ! = 0 ) {
client - > pers . teamInfo = qtrue ;
} else {
client - > pers . teamInfo = qfalse ;
}
}
# else
// teamInfo
s = Info_ValueForKey ( userinfo , " teamoverlay " ) ;
if ( ! * s | | atoi ( s ) ! = 0 ) {
client - > pers . teamInfo = qtrue ;
} else {
client - > pers . teamInfo = qfalse ;
}
# endif
/*
s = Info_ValueForKey ( userinfo , " cg_pmove_fixed " ) ;
if ( ! * s | | atoi ( s ) = = 0 ) {
client - > pers . pmoveFixed = qfalse ;
}
else {
client - > pers . pmoveFixed = qtrue ;
}
*/
// team task (0 = none, 1 = offence, 2 = defence)
teamTask = atoi ( Info_ValueForKey ( userinfo , " teamtask " ) ) ;
// team Leader (1 = leader, 0 is normal player)
teamLeader = client - > sess . teamLeader ;
// colors
2001-08-01 19:52:17 +00:00
strcpy ( c1 , Info_ValueForKey ( userinfo , " color1 " ) ) ;
strcpy ( c2 , Info_ValueForKey ( userinfo , " color2 " ) ) ;
2001-05-06 20:50:27 +00:00
strcpy ( redTeam , Info_ValueForKey ( userinfo , " g_redteam " ) ) ;
strcpy ( blueTeam , Info_ValueForKey ( userinfo , " g_blueteam " ) ) ;
// send over a subset of the userinfo keys so other clients can
// print scoreboards, display models, and play custom sounds
if ( ent - > r . svFlags & SVF_BOT ) {
2001-08-01 19:52:17 +00:00
s = va ( " n \\ %s \\ t \\ %i \\ model \\ %s \\ hmodel \\ %s \\ c1 \\ %s \\ c2 \\ %s \\ hc \\ %i \\ w \\ %i \\ l \\ %i \\ skill \\ %s \\ tt \\ %d \\ tl \\ %d " ,
client - > pers . netname , team , model , headModel , c1 , c2 ,
2001-05-06 20:50:27 +00:00
client - > pers . maxHealth , client - > sess . wins , client - > sess . losses ,
Info_ValueForKey ( userinfo , " skill " ) , teamTask , teamLeader ) ;
} else {
2001-08-01 19:52:17 +00:00
s = va ( " n \\ %s \\ t \\ %i \\ model \\ %s \\ hmodel \\ %s \\ g_redteam \\ %s \\ g_blueteam \\ %s \\ c1 \\ %s \\ c2 \\ %s \\ hc \\ %i \\ w \\ %i \\ l \\ %i \\ tt \\ %d \\ tl \\ %d " ,
client - > pers . netname , client - > sess . sessionTeam , model , headModel , redTeam , blueTeam , c1 , c2 ,
2001-05-06 20:50:27 +00:00
client - > pers . maxHealth , client - > sess . wins , client - > sess . losses , teamTask , teamLeader ) ;
}
trap_SetConfigstring ( CS_PLAYERS + clientNum , s ) ;
G_LogPrintf ( " ClientUserinfoChanged: %i %s \n " , clientNum , s ) ;
}
/*
= = = = = = = = = = =
ClientConnect
Called when a player begins connecting to the server .
Called again for every map change or tournement restart .
The session information will be valid after exit .
Return NULL if the client should be allowed , otherwise return
a string with the reason for denial .
Otherwise , the client will be sent the current gamestate
and will eventually get to ClientBegin .
firstTime will be qtrue the very first time a client connects
to the server machine , but qfalse on map changes and tournement
restarts .
= = = = = = = = = = = =
*/
char * ClientConnect ( int clientNum , qboolean firstTime , qboolean isBot ) {
char * value ;
// char *areabits;
gclient_t * client ;
char userinfo [ MAX_INFO_STRING ] ;
gentity_t * ent ;
ent = & g_entities [ clientNum ] ;
trap_GetUserinfo ( clientNum , userinfo , sizeof ( userinfo ) ) ;
// check to see if they are on the banned IP list
value = Info_ValueForKey ( userinfo , " ip " ) ;
if ( G_FilterPacket ( value ) ) {
return " Banned. " ;
}
2001-08-01 19:52:17 +00:00
if ( ! ( ent - > r . svFlags & SVF_BOT ) ) {
// check for a password
value = Info_ValueForKey ( userinfo , " password " ) ;
if ( g_password . string [ 0 ] & & Q_stricmp ( g_password . string , " none " ) & &
strcmp ( g_password . string , value ) ! = 0 ) {
return " Invalid password " ;
}
2001-05-06 20:50:27 +00:00
}
// they can connect
ent - > client = level . clients + clientNum ;
client = ent - > client ;
// areabits = client->areabits;
memset ( client , 0 , sizeof ( * client ) ) ;
client - > pers . connected = CON_CONNECTING ;
// read or initialize the session data
if ( firstTime | | level . newSession ) {
G_InitSessionData ( client , userinfo ) ;
}
G_ReadSessionData ( client ) ;
if ( isBot ) {
ent - > r . svFlags | = SVF_BOT ;
ent - > inuse = qtrue ;
if ( ! G_BotConnect ( clientNum , ! firstTime ) ) {
return " BotConnectfailed " ;
}
}
// get and distribute relevent paramters
G_LogPrintf ( " ClientConnect: %i \n " , clientNum ) ;
ClientUserinfoChanged ( clientNum ) ;
// don't do the "xxx connected" messages if they were caried over from previous level
if ( firstTime ) {
trap_SendServerCommand ( - 1 , va ( " print \" %s " S_COLOR_WHITE " connected \n \" " , client - > pers . netname ) ) ;
}
if ( g_gametype . integer > = GT_TEAM & &
client - > sess . sessionTeam ! = TEAM_SPECTATOR ) {
BroadcastTeamChange ( client , - 1 ) ;
}
// count current clients and rank for scoreboard
CalculateRanks ( ) ;
// for statistics
// client->areabits = areabits;
// if ( !client->areabits )
// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 );
return NULL ;
}
/*
= = = = = = = = = = =
ClientBegin
called when a client has finished connecting , and is ready
to be placed into the level . This will happen every level load ,
and on transition between teams , but doesn ' t happen on respawns
= = = = = = = = = = = =
*/
void ClientBegin ( int clientNum ) {
gentity_t * ent ;
gclient_t * client ;
2001-07-03 10:26:42 +00:00
//gentity_t *tent;
2001-05-06 20:50:27 +00:00
int flags ;
ent = g_entities + clientNum ;
client = level . clients + clientNum ;
if ( ent - > r . linked ) {
trap_UnlinkEntity ( ent ) ;
}
G_InitGentity ( ent ) ;
ent - > touch = 0 ;
ent - > pain = 0 ;
ent - > client = client ;
client - > pers . connected = CON_CONNECTED ;
client - > pers . enterTime = level . time ;
client - > pers . teamState . state = TEAM_BEGIN ;
// save eflags around this, because changing teams will
// cause this to happen with a valid entity, and we
// want to make sure the teleport bit is set right
// so the viewpoint doesn't interpolate through the
// world to the new position
flags = client - > ps . eFlags ;
memset ( & client - > ps , 0 , sizeof ( client - > ps ) ) ;
client - > ps . eFlags = flags ;
// locate ent at a spawn point
ClientSpawn ( ent ) ;
if ( client - > sess . sessionTeam ! = TEAM_SPECTATOR ) {
// send event
2001-06-08 04:47:30 +00:00
//Elder: removed
//tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
2001-07-03 10:26:42 +00:00
//tent->s.clientNum = ent->s.clientNum;
//Elder: moved after ClientSpawn call
//Elder: added to initialize weaponmodes
client - > ps . persistant [ PERS_WEAPONMODES ] | = RQ3_GRENSHORT ; //set to short range
client - > ps . persistant [ PERS_WEAPONMODES ] | = RQ3_KNIFEMODE ; //set to slash attack
//Elder: debug
//G_Printf("In clientbegin- PERS_WEAPONMODES: %d\n", ent->client->ps.persistant[PERS_WEAPONMODES]);
2001-05-06 20:50:27 +00:00
if ( g_gametype . integer ! = GT_TOURNAMENT ) {
trap_SendServerCommand ( - 1 , va ( " print \" %s " S_COLOR_WHITE " entered the game \n \" " , client - > pers . netname ) ) ;
}
}
G_LogPrintf ( " ClientBegin: %i \n " , clientNum ) ;
// count current clients and rank for scoreboard
CalculateRanks ( ) ;
}
/*
= = = = = = = = = = =
ClientSpawn
Called every time a client is placed fresh in the world :
after the first ClientBegin , and after each respawn
Initializes all non - persistant parts of playerState
= = = = = = = = = = = =
*/
void ClientSpawn ( gentity_t * ent ) {
int index ;
vec3_t spawn_origin , spawn_angles ;
gclient_t * client ;
int i ;
clientPersistant_t saved ;
clientSession_t savedSess ;
int persistant [ MAX_PERSISTANT ] ;
gentity_t * spawnPoint ;
int flags ;
int savedPing ;
// char *savedAreaBits;
int accuracy_hits , accuracy_shots ;
2001-08-03 22:46:49 +00:00
int knifeShots = 0 ;
int knifeHits = 0 ;
int mk23Shots = 0 ;
int mk23Hits = 0 ;
int m4Shots = 0 ;
int m4Hits = 0 ;
int mp5Shots = 0 ;
int mp5Hits = 0 ;
int m3Shots = 0 ;
int m3Hits = 0 ;
int hcShots = 0 ;
int hcHits = 0 ;
int ssgShots = 0 ;
int ssgHits = 0 ;
int akimboShots = 0 ;
int akimboHits = 0 ;
int grenShots = 0 ;
int grenHits = 0 ;
2001-05-06 20:50:27 +00:00
int eventSequence ;
char userinfo [ MAX_INFO_STRING ] ;
index = ent - g_entities ;
client = ent - > client ;
// find a spawn point
// do it before setting health back up, so farthest
// ranging doesn't count this client
if ( client - > sess . sessionTeam = = TEAM_SPECTATOR ) {
spawnPoint = SelectSpectatorSpawnPoint (
spawn_origin , spawn_angles ) ;
} else if ( g_gametype . integer > = GT_CTF ) {
// all base oriented team games use the CTF spawn points
spawnPoint = SelectCTFSpawnPoint (
client - > sess . sessionTeam ,
client - > pers . teamState . state ,
spawn_origin , spawn_angles ) ;
} else {
do {
// the first spawn should be at a good looking spot
if ( ! client - > pers . initialSpawn & & client - > pers . localClient ) {
client - > pers . initialSpawn = qtrue ;
spawnPoint = SelectInitialSpawnPoint ( spawn_origin , spawn_angles ) ;
} else {
// don't spawn near existing origin if possible
spawnPoint = SelectSpawnPoint (
client - > ps . origin ,
spawn_origin , spawn_angles ) ;
}
// Tim needs to prevent bots from spawning at the initial point
// on q3dm0...
if ( ( spawnPoint - > flags & FL_NO_BOTS ) & & ( ent - > r . svFlags & SVF_BOT ) ) {
continue ; // try again
}
// just to be symetric, we have a nohumans option...
if ( ( spawnPoint - > flags & FL_NO_HUMANS ) & & ! ( ent - > r . svFlags & SVF_BOT ) ) {
continue ; // try again
}
break ;
} while ( 1 ) ;
}
client - > pers . teamState . state = TEAM_ACTIVE ;
// always clear the kamikaze flag
ent - > s . eFlags & = ~ EF_KAMIKAZE ;
// toggle the teleport bit so the client knows to not lerp
// and never clear the voted flag
flags = ent - > client - > ps . eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED ) ;
flags ^ = EF_TELEPORT_BIT ;
// clear everything but the persistant data
saved = client - > pers ;
savedSess = client - > sess ;
savedPing = client - > ps . ping ;
// savedAreaBits = client->areabits;
accuracy_hits = client - > accuracy_hits ;
accuracy_shots = client - > accuracy_shots ;
2001-08-03 22:46:49 +00:00
knifeShots = client - > knifeShots ;
knifeHits = client - > knifeHits ;
mk23Shots = client - > mk23Shots ;
mk23Hits = client - > mk23Hits ;
m4Shots = client - > m4Shots ;
m4Hits = client - > m4Hits ;
mp5Shots = client - > mp5Shots ;
mp5Hits = client - > mp5Hits ;
m3Shots = client - > m3Shots ;
m3Hits = client - > m3Hits ;
hcShots = client - > hcShots ;
hcHits = client - > hcHits ;
akimboShots = client - > akimboShots ;
akimboHits = client - > akimboHits ;
grenShots = client - > grenShots ;
grenHits = client - > grenHits ;
2001-05-06 20:50:27 +00:00
for ( i = 0 ; i < MAX_PERSISTANT ; i + + ) {
persistant [ i ] = client - > ps . persistant [ i ] ;
}
eventSequence = client - > ps . eventSequence ;
2001-08-01 19:52:17 +00:00
memset ( client , 0 , sizeof ( * client ) ) ; // bk FIXME: Com_Memset?
2001-05-06 20:50:27 +00:00
client - > pers = saved ;
client - > sess = savedSess ;
client - > ps . ping = savedPing ;
// client->areabits = savedAreaBits;
client - > accuracy_hits = accuracy_hits ;
client - > accuracy_shots = accuracy_shots ;
client - > lastkilled_client = - 1 ;
2001-06-11 23:02:21 +00:00
2001-05-06 20:50:27 +00:00
for ( i = 0 ; i < MAX_PERSISTANT ; i + + ) {
client - > ps . persistant [ i ] = persistant [ i ] ;
}
client - > ps . eventSequence = eventSequence ;
// increment the spawncount so the client will detect the respawn
client - > ps . persistant [ PERS_SPAWN_COUNT ] + + ;
client - > ps . persistant [ PERS_TEAM ] = client - > sess . sessionTeam ;
client - > airOutTime = level . time + 12000 ;
trap_GetUserinfo ( index , userinfo , sizeof ( userinfo ) ) ;
// set max health
client - > pers . maxHealth = atoi ( Info_ValueForKey ( userinfo , " handicap " ) ) ;
if ( client - > pers . maxHealth < 1 | | client - > pers . maxHealth > 100 ) {
client - > pers . maxHealth = 100 ;
}
// clear entity values
//client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
client - > ps . eFlags = flags ;
ent - > s . groundEntityNum = ENTITYNUM_NONE ;
ent - > client = & level . clients [ index ] ;
ent - > takedamage = qtrue ;
ent - > inuse = qtrue ;
ent - > classname = " player " ;
ent - > r . contents = CONTENTS_BODY ;
ent - > clipmask = MASK_PLAYERSOLID ;
ent - > die = player_die ;
ent - > waterlevel = 0 ;
ent - > watertype = 0 ;
ent - > flags = 0 ;
VectorCopy ( playerMins , ent - > r . mins ) ;
VectorCopy ( playerMaxs , ent - > r . maxs ) ;
client - > ps . clientNum = index ;
//Blaze: changed WP_MACHINEGUN to WP_PISTOL, makes the base weapon you start with the pistol
client - > ps . stats [ STAT_WEAPONS ] = ( 1 < < WP_PISTOL ) ;
//Blaze: Set starting amo for the machine gun, different in teamplay and dm, we can remove this
/*
client - > ps . stats [ STAT_WEAPONS ] = ( 1 < < WP_MACHINEGUN ) ;
if ( g_gametype . integer = = GT_TEAM ) {
client - > ps . ammo [ WP_MACHINEGUN ] = 50 ;
} else {
client - > ps . ammo [ WP_MACHINEGUN ] = 100 ;
}
*/
// Begin Duffman
// Initial amount of ammo when spawning, this will be changed when weapons are added.
//Blaze: Changed from WP_MACHINEGUN to WP_PISTOL
2001-06-08 04:47:30 +00:00
//Elder: changed to Ammo function instead of Reload
client - > ps . ammo [ WP_PISTOL ] = ClipAmountForAmmo ( WP_PISTOL ) ;
2001-05-06 20:50:27 +00:00
client - > numClips [ WP_PISTOL ] = 0 ;
// End Duffman
//Blaze: Changed WP_GAUNTLET to WP_KNIFE
client - > ps . stats [ STAT_WEAPONS ] | = ( 1 < < WP_KNIFE ) ;
//Blaze: 1 knife to start with
client - > ps . ammo [ WP_KNIFE ] = 1 ;
//Blaze: No Grappling Hook in reaction
// client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
//Blaze: Set the bandage variable to 0
client - > bleedtick = 0 ;
// health will count down towards max_health
ent - > health = client - > ps . stats [ STAT_HEALTH ] = 100 ; // max health of 100 client->ps.stats[STAT_MAX_HEALTH];//Blaze: removed * 1.25 becase we wanna start at 100 health
2001-06-25 23:46:12 +00:00
// reset streak count
client - > ps . stats [ STAT_STREAK ] = 0 ;
2001-05-06 20:50:27 +00:00
G_SetOrigin ( ent , spawn_origin ) ;
VectorCopy ( spawn_origin , client - > ps . origin ) ;
// the respawned flag will be cleared after the attack and jump keys come up
client - > ps . pm_flags | = PMF_RESPAWNED ;
trap_GetUsercmd ( client - level . clients , & ent - > client - > pers . cmd ) ;
SetClientViewAngle ( ent , spawn_angles ) ;
if ( ent - > client - > sess . sessionTeam = = TEAM_SPECTATOR ) {
} else {
G_KillBox ( ent ) ;
trap_LinkEntity ( ent ) ;
// force the base weapon up
//Blaze: Changed WP_MACHINEGUN to WP_PISTOL
client - > ps . weapon = WP_PISTOL ;
client - > ps . weaponstate = WEAPON_READY ;
}
//Blaze: Set the opendoor flag to 0
client - > openDoor = qfalse ;
2001-08-13 17:26:53 +00:00
client - > openDoorTime = 0 ;
2001-05-06 20:50:27 +00:00
// don't allow full run speed for a bit
client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
client - > ps . pm_time = 100 ;
client - > respawnTime = level . time ;
client - > inactivityTime = level . time + g_inactivity . integer * 1000 ;
client - > latched_buttons = 0 ;
2001-06-18 01:59:58 +00:00
//Elder: reset all RQ3 non-persistent stats
ent - > client - > ps . stats [ STAT_RQ3 ] = 0 ;
2001-07-03 10:26:42 +00:00
2001-06-18 01:59:58 +00:00
//Elder: set weaponfireNextTime amount
client - > weaponfireNextTime = 0 ;
2001-07-03 10:26:42 +00:00
2001-06-24 02:08:15 +00:00
//Elder: Initialize fast reloads stuff
client - > fastReloads = 0 ;
client - > lastReloadTime = 0 ;
//Elder: initialize consecutive shots for M4 ride-up
client - > consecutiveShots = 0 ;
2001-05-06 20:50:27 +00:00
// set default animations
client - > ps . torsoAnim = TORSO_STAND ;
client - > ps . legsAnim = LEGS_IDLE ;
2001-08-17 20:48:18 +00:00
// weapon animations
client - > ps . generic1 = ( ( client - > ps . generic1 & ANIM_TOGGLEBIT )
^ ANIM_TOGGLEBIT ) | WP_ANIM_IDLE ;
2001-08-06 19:15:00 +00:00
2001-05-06 20:50:27 +00:00
if ( level . intermissiontime ) {
MoveClientToIntermission ( ent ) ;
} else {
// fire the targets of the spawn point
G_UseTargets ( spawnPoint , ent ) ;
// select the highest weapon number available, after any
// spawn given items have fired
client - > ps . weapon = 1 ;
for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i - - ) {
if ( client - > ps . stats [ STAT_WEAPONS ] & ( 1 < < i ) ) {
client - > ps . weapon = i ;
break ;
}
}
}
// run a client frame to drop exactly to the floor,
// initialize animations and other things
client - > ps . commandTime = level . time - 100 ;
ent - > client - > pers . cmd . serverTime = level . time ;
ClientThink ( ent - g_entities ) ;
// positively link the client, even if the command times are weird
if ( ent - > client - > sess . sessionTeam ! = TEAM_SPECTATOR ) {
BG_PlayerStateToEntityState ( & client - > ps , & ent - > s , qtrue ) ;
VectorCopy ( ent - > client - > ps . origin , ent - > r . currentOrigin ) ;
trap_LinkEntity ( ent ) ;
}
// run the presend to set anything else
ClientEndFrame ( ent ) ;
// clear entity state values
BG_PlayerStateToEntityState ( & client - > ps , & ent - > s , qtrue ) ;
}
/*
= = = = = = = = = = =
ClientDisconnect
Called when a player drops from the server .
Will not be called between levels .
This should NOT be called directly by any game logic ,
call trap_DropClient ( ) , which will call this and do
server system housekeeping .
= = = = = = = = = = = =
*/
void ClientDisconnect ( int clientNum ) {
gentity_t * ent ;
gentity_t * tent ;
int i ;
// cleanup if we are kicking a bot that
// hasn't spawned yet
G_RemoveQueuedBotBegin ( clientNum ) ;
ent = g_entities + clientNum ;
if ( ! ent - > client ) {
return ;
}
// stop any following clients
for ( i = 0 ; i < level . maxclients ; i + + ) {
if ( level . clients [ i ] . sess . sessionTeam = = TEAM_SPECTATOR
& & level . clients [ i ] . sess . spectatorState = = SPECTATOR_FOLLOW
& & level . clients [ i ] . sess . spectatorClient = = clientNum ) {
StopFollowing ( & g_entities [ i ] ) ;
}
}
// send effect if they were completely connected
if ( ent - > client - > pers . connected = = CON_CONNECTED
& & ent - > client - > sess . sessionTeam ! = TEAM_SPECTATOR ) {
tent = G_TempEntity ( ent - > client - > ps . origin , EV_PLAYER_TELEPORT_OUT ) ;
tent - > s . clientNum = ent - > s . clientNum ;
// They don't get to take powerups with them!
// Especially important for stuff like CTF flags
TossClientItems ( ent ) ;
# ifdef MISSIONPACK
TossClientPersistantPowerups ( ent ) ;
if ( g_gametype . integer = = GT_HARVESTER ) {
TossClientCubes ( ent ) ;
}
# endif
}
G_LogPrintf ( " ClientDisconnect: %i \n " , clientNum ) ;
// if we are playing in tourney mode and losing, give a win to the other player
if ( ( g_gametype . integer = = GT_TOURNAMENT )
& & ! level . intermissiontime
& & ! level . warmupTime & & level . sortedClients [ 1 ] = = clientNum ) {
level . clients [ level . sortedClients [ 0 ] ] . sess . wins + + ;
ClientUserinfoChanged ( level . sortedClients [ 0 ] ) ;
}
trap_UnlinkEntity ( ent ) ;
ent - > s . modelindex = 0 ;
ent - > inuse = qfalse ;
ent - > classname = " disconnected " ;
ent - > client - > pers . connected = CON_DISCONNECTED ;
ent - > client - > ps . persistant [ PERS_TEAM ] = TEAM_FREE ;
ent - > client - > sess . sessionTeam = TEAM_FREE ;
trap_SetConfigstring ( CS_PLAYERS + clientNum , " " ) ;
CalculateRanks ( ) ;
if ( ent - > r . svFlags & SVF_BOT ) {
BotAIShutdownClient ( clientNum , qfalse ) ;
}
}