Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_client.c (33822B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 //
     23 #include "g_local.h"
     24 
     25 // g_client.c -- client functions that don't happen every frame
     26 
     27 static vec3_t	playerMins = {-15, -15, -24};
     28 static vec3_t	playerMaxs = {15, 15, 32};
     29 
     30 /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
     31 potential spawning position for deathmatch games.
     32 The first time a player enters the game, they will be at an 'initial' spot.
     33 Targets will be fired when someone spawns in on them.
     34 "nobots" will prevent bots from using this spot.
     35 "nohumans" will prevent non-bots from using this spot.
     36 */
     37 void SP_info_player_deathmatch( gentity_t *ent ) {
     38 	int		i;
     39 
     40 	G_SpawnInt( "nobots", "0", &i);
     41 	if ( i ) {
     42 		ent->flags |= FL_NO_BOTS;
     43 	}
     44 	G_SpawnInt( "nohumans", "0", &i );
     45 	if ( i ) {
     46 		ent->flags |= FL_NO_HUMANS;
     47 	}
     48 }
     49 
     50 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
     51 equivelant to info_player_deathmatch
     52 */
     53 void SP_info_player_start(gentity_t *ent) {
     54 	ent->classname = "info_player_deathmatch";
     55 	SP_info_player_deathmatch( ent );
     56 }
     57 
     58 /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
     59 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
     60 */
     61 void SP_info_player_intermission( gentity_t *ent ) {
     62 
     63 }
     64 
     65 
     66 
     67 /*
     68 =======================================================================
     69 
     70   SelectSpawnPoint
     71 
     72 =======================================================================
     73 */
     74 
     75 /*
     76 ================
     77 SpotWouldTelefrag
     78 
     79 ================
     80 */
     81 qboolean SpotWouldTelefrag( gentity_t *spot ) {
     82 	int			i, num;
     83 	int			touch[MAX_GENTITIES];
     84 	gentity_t	*hit;
     85 	vec3_t		mins, maxs;
     86 
     87 	VectorAdd( spot->s.origin, playerMins, mins );
     88 	VectorAdd( spot->s.origin, playerMaxs, maxs );
     89 	num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
     90 
     91 	for (i=0 ; i<num ; i++) {
     92 		hit = &g_entities[touch[i]];
     93 		//if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
     94 		if ( hit->client) {
     95 			return qtrue;
     96 		}
     97 
     98 	}
     99 
    100 	return qfalse;
    101 }
    102 
    103 /*
    104 ================
    105 SelectNearestDeathmatchSpawnPoint
    106 
    107 Find the spot that we DON'T want to use
    108 ================
    109 */
    110 #define	MAX_SPAWN_POINTS	128
    111 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) {
    112 	gentity_t	*spot;
    113 	vec3_t		delta;
    114 	float		dist, nearestDist;
    115 	gentity_t	*nearestSpot;
    116 
    117 	nearestDist = 999999;
    118 	nearestSpot = NULL;
    119 	spot = NULL;
    120 
    121 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
    122 
    123 		VectorSubtract( spot->s.origin, from, delta );
    124 		dist = VectorLength( delta );
    125 		if ( dist < nearestDist ) {
    126 			nearestDist = dist;
    127 			nearestSpot = spot;
    128 		}
    129 	}
    130 
    131 	return nearestSpot;
    132 }
    133 
    134 
    135 /*
    136 ================
    137 SelectRandomDeathmatchSpawnPoint
    138 
    139 go to a random point that doesn't telefrag
    140 ================
    141 */
    142 #define	MAX_SPAWN_POINTS	128
    143 gentity_t *SelectRandomDeathmatchSpawnPoint( void ) {
    144 	gentity_t	*spot;
    145 	int			count;
    146 	int			selection;
    147 	gentity_t	*spots[MAX_SPAWN_POINTS];
    148 
    149 	count = 0;
    150 	spot = NULL;
    151 
    152 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
    153 		if ( SpotWouldTelefrag( spot ) ) {
    154 			continue;
    155 		}
    156 		spots[ count ] = spot;
    157 		count++;
    158 	}
    159 
    160 	if ( !count ) {	// no spots that won't telefrag
    161 		return G_Find( NULL, FOFS(classname), "info_player_deathmatch");
    162 	}
    163 
    164 	selection = rand() % count;
    165 	return spots[ selection ];
    166 }
    167 
    168 /*
    169 ===========
    170 SelectRandomFurthestSpawnPoint
    171 
    172 Chooses a player start, deathmatch start, etc
    173 ============
    174 */
    175 gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) {
    176 	gentity_t	*spot;
    177 	vec3_t		delta;
    178 	float		dist;
    179 	float		list_dist[64];
    180 	gentity_t	*list_spot[64];
    181 	int			numSpots, rnd, i, j;
    182 
    183 	numSpots = 0;
    184 	spot = NULL;
    185 
    186 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
    187 		if ( SpotWouldTelefrag( spot ) ) {
    188 			continue;
    189 		}
    190 		VectorSubtract( spot->s.origin, avoidPoint, delta );
    191 		dist = VectorLength( delta );
    192 		for (i = 0; i < numSpots; i++) {
    193 			if ( dist > list_dist[i] ) {
    194 				if ( numSpots >= 64 )
    195 					numSpots = 64-1;
    196 				for (j = numSpots; j > i; j--) {
    197 					list_dist[j] = list_dist[j-1];
    198 					list_spot[j] = list_spot[j-1];
    199 				}
    200 				list_dist[i] = dist;
    201 				list_spot[i] = spot;
    202 				numSpots++;
    203 				if (numSpots > 64)
    204 					numSpots = 64;
    205 				break;
    206 			}
    207 		}
    208 		if (i >= numSpots && numSpots < 64) {
    209 			list_dist[numSpots] = dist;
    210 			list_spot[numSpots] = spot;
    211 			numSpots++;
    212 		}
    213 	}
    214 	if (!numSpots) {
    215 		spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
    216 		if (!spot)
    217 			G_Error( "Couldn't find a spawn point" );
    218 		VectorCopy (spot->s.origin, origin);
    219 		origin[2] += 9;
    220 		VectorCopy (spot->s.angles, angles);
    221 		return spot;
    222 	}
    223 
    224 	// select a random spot from the spawn points furthest away
    225 	rnd = random() * (numSpots / 2);
    226 
    227 	VectorCopy (list_spot[rnd]->s.origin, origin);
    228 	origin[2] += 9;
    229 	VectorCopy (list_spot[rnd]->s.angles, angles);
    230 
    231 	return list_spot[rnd];
    232 }
    233 
    234 /*
    235 ===========
    236 SelectSpawnPoint
    237 
    238 Chooses a player start, deathmatch start, etc
    239 ============
    240 */
    241 gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) {
    242 	return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles );
    243 
    244 	/*
    245 	gentity_t	*spot;
    246 	gentity_t	*nearestSpot;
    247 
    248 	nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint );
    249 
    250 	spot = SelectRandomDeathmatchSpawnPoint ( );
    251 	if ( spot == nearestSpot ) {
    252 		// roll again if it would be real close to point of death
    253 		spot = SelectRandomDeathmatchSpawnPoint ( );
    254 		if ( spot == nearestSpot ) {
    255 			// last try
    256 			spot = SelectRandomDeathmatchSpawnPoint ( );
    257 		}		
    258 	}
    259 
    260 	// find a single player start spot
    261 	if (!spot) {
    262 		G_Error( "Couldn't find a spawn point" );
    263 	}
    264 
    265 	VectorCopy (spot->s.origin, origin);
    266 	origin[2] += 9;
    267 	VectorCopy (spot->s.angles, angles);
    268 
    269 	return spot;
    270 	*/
    271 }
    272 
    273 /*
    274 ===========
    275 SelectInitialSpawnPoint
    276 
    277 Try to find a spawn point marked 'initial', otherwise
    278 use normal spawn selection.
    279 ============
    280 */
    281 gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) {
    282 	gentity_t	*spot;
    283 
    284 	spot = NULL;
    285 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
    286 		if ( spot->spawnflags & 1 ) {
    287 			break;
    288 		}
    289 	}
    290 
    291 	if ( !spot || SpotWouldTelefrag( spot ) ) {
    292 		return SelectSpawnPoint( vec3_origin, origin, angles );
    293 	}
    294 
    295 	VectorCopy (spot->s.origin, origin);
    296 	origin[2] += 9;
    297 	VectorCopy (spot->s.angles, angles);
    298 
    299 	return spot;
    300 }
    301 
    302 /*
    303 ===========
    304 SelectSpectatorSpawnPoint
    305 
    306 ============
    307 */
    308 gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) {
    309 	FindIntermissionPoint();
    310 
    311 	VectorCopy( level.intermission_origin, origin );
    312 	VectorCopy( level.intermission_angle, angles );
    313 
    314 	return NULL;
    315 }
    316 
    317 /*
    318 =======================================================================
    319 
    320 BODYQUE
    321 
    322 =======================================================================
    323 */
    324 
    325 /*
    326 ===============
    327 InitBodyQue
    328 ===============
    329 */
    330 void InitBodyQue (void) {
    331 	int		i;
    332 	gentity_t	*ent;
    333 
    334 	level.bodyQueIndex = 0;
    335 	for (i=0; i<BODY_QUEUE_SIZE ; i++) {
    336 		ent = G_Spawn();
    337 		ent->classname = "bodyque";
    338 		ent->neverFree = qtrue;
    339 		level.bodyQue[i] = ent;
    340 	}
    341 }
    342 
    343 /*
    344 =============
    345 BodySink
    346 
    347 After sitting around for five seconds, fall into the ground and dissapear
    348 =============
    349 */
    350 void BodySink( gentity_t *ent ) {
    351 	if ( level.time - ent->timestamp > 6500 ) {
    352 		// the body ques are never actually freed, they are just unlinked
    353 		trap_UnlinkEntity( ent );
    354 		ent->physicsObject = qfalse;
    355 		return;	
    356 	}
    357 	ent->nextthink = level.time + 100;
    358 	ent->s.pos.trBase[2] -= 1;
    359 }
    360 
    361 /*
    362 =============
    363 CopyToBodyQue
    364 
    365 A player is respawning, so make an entity that looks
    366 just like the existing corpse to leave behind.
    367 =============
    368 */
    369 void CopyToBodyQue( gentity_t *ent ) {
    370 #ifdef MISSIONPACK
    371 	gentity_t	*e;
    372 	int i;
    373 #endif
    374 	gentity_t		*body;
    375 	int			contents;
    376 
    377 	trap_UnlinkEntity (ent);
    378 
    379 	// if client is in a nodrop area, don't leave the body
    380 	contents = trap_PointContents( ent->s.origin, -1 );
    381 	if ( contents & CONTENTS_NODROP ) {
    382 		return;
    383 	}
    384 
    385 	// grab a body que and cycle to the next one
    386 	body = level.bodyQue[ level.bodyQueIndex ];
    387 	level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE;
    388 
    389 	trap_UnlinkEntity (body);
    390 
    391 	body->s = ent->s;
    392 	body->s.eFlags = EF_DEAD;		// clear EF_TALK, etc
    393 #ifdef MISSIONPACK
    394 	if ( ent->s.eFlags & EF_KAMIKAZE ) {
    395 		body->s.eFlags |= EF_KAMIKAZE;
    396 
    397 		// check if there is a kamikaze timer around for this owner
    398 		for (i = 0; i < MAX_GENTITIES; i++) {
    399 			e = &g_entities[i];
    400 			if (!e->inuse)
    401 				continue;
    402 			if (e->activator != ent)
    403 				continue;
    404 			if (strcmp(e->classname, "kamikaze timer"))
    405 				continue;
    406 			e->activator = body;
    407 			break;
    408 		}
    409 	}
    410 #endif
    411 	body->s.powerups = 0;	// clear powerups
    412 	body->s.loopSound = 0;	// clear lava burning
    413 	body->s.number = body - g_entities;
    414 	body->timestamp = level.time;
    415 	body->physicsObject = qtrue;
    416 	body->physicsBounce = 0;		// don't bounce
    417 	if ( body->s.groundEntityNum == ENTITYNUM_NONE ) {
    418 		body->s.pos.trType = TR_GRAVITY;
    419 		body->s.pos.trTime = level.time;
    420 		VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
    421 	} else {
    422 		body->s.pos.trType = TR_STATIONARY;
    423 	}
    424 	body->s.event = 0;
    425 
    426 	// change the animation to the last-frame only, so the sequence
    427 	// doesn't repeat anew for the body
    428 	switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) {
    429 	case BOTH_DEATH1:
    430 	case BOTH_DEAD1:
    431 		body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
    432 		break;
    433 	case BOTH_DEATH2:
    434 	case BOTH_DEAD2:
    435 		body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
    436 		break;
    437 	case BOTH_DEATH3:
    438 	case BOTH_DEAD3:
    439 	default:
    440 		body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3;
    441 		break;
    442 	}
    443 
    444 	body->r.svFlags = ent->r.svFlags;
    445 	VectorCopy (ent->r.mins, body->r.mins);
    446 	VectorCopy (ent->r.maxs, body->r.maxs);
    447 	VectorCopy (ent->r.absmin, body->r.absmin);
    448 	VectorCopy (ent->r.absmax, body->r.absmax);
    449 
    450 	body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
    451 	body->r.contents = CONTENTS_CORPSE;
    452 	body->r.ownerNum = ent->s.number;
    453 
    454 	body->nextthink = level.time + 5000;
    455 	body->think = BodySink;
    456 
    457 	body->die = body_die;
    458 
    459 	// don't take more damage if already gibbed
    460 	if ( ent->health <= GIB_HEALTH ) {
    461 		body->takedamage = qfalse;
    462 	} else {
    463 		body->takedamage = qtrue;
    464 	}
    465 
    466 
    467 	VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
    468 	trap_LinkEntity (body);
    469 }
    470 
    471 //======================================================================
    472 
    473 
    474 /*
    475 ==================
    476 SetClientViewAngle
    477 
    478 ==================
    479 */
    480 void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
    481 	int			i;
    482 
    483 	// set the delta angle
    484 	for (i=0 ; i<3 ; i++) {
    485 		int		cmdAngle;
    486 
    487 		cmdAngle = ANGLE2SHORT(angle[i]);
    488 		ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i];
    489 	}
    490 	VectorCopy( angle, ent->s.angles );
    491 	VectorCopy (ent->s.angles, ent->client->ps.viewangles);
    492 }
    493 
    494 /*
    495 ================
    496 respawn
    497 ================
    498 */
    499 void respawn( gentity_t *ent ) {
    500 	gentity_t	*tent;
    501 
    502 	CopyToBodyQue (ent);
    503 	ClientSpawn(ent);
    504 
    505 	// add a teleportation effect
    506 	tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
    507 	tent->s.clientNum = ent->s.clientNum;
    508 }
    509 
    510 /*
    511 ================
    512 TeamCount
    513 
    514 Returns number of players on a team
    515 ================
    516 */
    517 team_t TeamCount( int ignoreClientNum, int team ) {
    518 	int		i;
    519 	int		count = 0;
    520 
    521 	for ( i = 0 ; i < level.maxclients ; i++ ) {
    522 		if ( i == ignoreClientNum ) {
    523 			continue;
    524 		}
    525 		if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
    526 			continue;
    527 		}
    528 		if ( level.clients[i].sess.sessionTeam == team ) {
    529 			count++;
    530 		}
    531 	}
    532 
    533 	return count;
    534 }
    535 
    536 /*
    537 ================
    538 TeamLeader
    539 
    540 Returns the client number of the team leader
    541 ================
    542 */
    543 int TeamLeader( int team ) {
    544 	int		i;
    545 
    546 	for ( i = 0 ; i < level.maxclients ; i++ ) {
    547 		if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
    548 			continue;
    549 		}
    550 		if ( level.clients[i].sess.sessionTeam == team ) {
    551 			if ( level.clients[i].sess.teamLeader )
    552 				return i;
    553 		}
    554 	}
    555 
    556 	return -1;
    557 }
    558 
    559 
    560 /*
    561 ================
    562 PickTeam
    563 
    564 ================
    565 */
    566 team_t PickTeam( int ignoreClientNum ) {
    567 	int		counts[TEAM_NUM_TEAMS];
    568 
    569 	counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE );
    570 	counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED );
    571 
    572 	if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) {
    573 		return TEAM_RED;
    574 	}
    575 	if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) {
    576 		return TEAM_BLUE;
    577 	}
    578 	// equal team count, so join the team with the lowest score
    579 	if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) {
    580 		return TEAM_RED;
    581 	}
    582 	return TEAM_BLUE;
    583 }
    584 
    585 /*
    586 ===========
    587 ForceClientSkin
    588 
    589 Forces a client's skin (for teamplay)
    590 ===========
    591 */
    592 /*
    593 static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) {
    594 	char *p;
    595 
    596 	if ((p = Q_strrchr(model, '/')) != 0) {
    597 		*p = 0;
    598 	}
    599 
    600 	Q_strcat(model, MAX_QPATH, "/");
    601 	Q_strcat(model, MAX_QPATH, skin);
    602 }
    603 */
    604 
    605 /*
    606 ===========
    607 ClientCheckName
    608 ============
    609 */
    610 static void ClientCleanName( const char *in, char *out, int outSize ) {
    611 	int		len, colorlessLen;
    612 	char	ch;
    613 	char	*p;
    614 	int		spaces;
    615 
    616 	//save room for trailing null byte
    617 	outSize--;
    618 
    619 	len = 0;
    620 	colorlessLen = 0;
    621 	p = out;
    622 	*p = 0;
    623 	spaces = 0;
    624 
    625 	while( 1 ) {
    626 		ch = *in++;
    627 		if( !ch ) {
    628 			break;
    629 		}
    630 
    631 		// don't allow leading spaces
    632 		if( !*p && ch == ' ' ) {
    633 			continue;
    634 		}
    635 
    636 		// check colors
    637 		if( ch == Q_COLOR_ESCAPE ) {
    638 			// solo trailing carat is not a color prefix
    639 			if( !*in ) {
    640 				break;
    641 			}
    642 
    643 			// don't allow black in a name, period
    644 			if( ColorIndex(*in) == 0 ) {
    645 				in++;
    646 				continue;
    647 			}
    648 
    649 			// make sure room in dest for both chars
    650 			if( len > outSize - 2 ) {
    651 				break;
    652 			}
    653 
    654 			*out++ = ch;
    655 			*out++ = *in++;
    656 			len += 2;
    657 			continue;
    658 		}
    659 
    660 		// don't allow too many consecutive spaces
    661 		if( ch == ' ' ) {
    662 			spaces++;
    663 			if( spaces > 3 ) {
    664 				continue;
    665 			}
    666 		}
    667 		else {
    668 			spaces = 0;
    669 		}
    670 
    671 		if( len > outSize - 1 ) {
    672 			break;
    673 		}
    674 
    675 		*out++ = ch;
    676 		colorlessLen++;
    677 		len++;
    678 	}
    679 	*out = 0;
    680 
    681 	// don't allow empty names
    682 	if( *p == 0 || colorlessLen == 0 ) {
    683 		Q_strncpyz( p, "UnnamedPlayer", outSize );
    684 	}
    685 }
    686 
    687 
    688 /*
    689 ===========
    690 ClientUserInfoChanged
    691 
    692 Called from ClientConnect when the player first connects and
    693 directly by the server system when the player updates a userinfo variable.
    694 
    695 The game can override any of the settings and call trap_SetUserinfo
    696 if desired.
    697 ============
    698 */
    699 void ClientUserinfoChanged( int clientNum ) {
    700 	gentity_t *ent;
    701 	int		teamTask, teamLeader, team, health;
    702 	char	*s;
    703 	char	model[MAX_QPATH];
    704 	char	headModel[MAX_QPATH];
    705 	char	oldname[MAX_STRING_CHARS];
    706 	gclient_t	*client;
    707 	char	c1[MAX_INFO_STRING];
    708 	char	c2[MAX_INFO_STRING];
    709 	char	redTeam[MAX_INFO_STRING];
    710 	char	blueTeam[MAX_INFO_STRING];
    711 	char	userinfo[MAX_INFO_STRING];
    712 
    713 	ent = g_entities + clientNum;
    714 	client = ent->client;
    715 
    716 	trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
    717 
    718 	// check for malformed or illegal info strings
    719 	if ( !Info_Validate(userinfo) ) {
    720 		strcpy (userinfo, "\\name\\badinfo");
    721 	}
    722 
    723 	// check for local client
    724 	s = Info_ValueForKey( userinfo, "ip" );
    725 	if ( !strcmp( s, "localhost" ) ) {
    726 		client->pers.localClient = qtrue;
    727 	}
    728 
    729 	// check the item prediction
    730 	s = Info_ValueForKey( userinfo, "cg_predictItems" );
    731 	if ( !atoi( s ) ) {
    732 		client->pers.predictItemPickup = qfalse;
    733 	} else {
    734 		client->pers.predictItemPickup = qtrue;
    735 	}
    736 
    737 	// set name
    738 	Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
    739 	s = Info_ValueForKey (userinfo, "name");
    740 	ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) );
    741 
    742 	if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
    743 		if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
    744 			Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) );
    745 		}
    746 	}
    747 
    748 	if ( client->pers.connected == CON_CONNECTED ) {
    749 		if ( strcmp( oldname, client->pers.netname ) ) {
    750 			trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, 
    751 				client->pers.netname) );
    752 		}
    753 	}
    754 
    755 	// set max health
    756 #ifdef MISSIONPACK
    757 	if (client->ps.powerups[PW_GUARD]) {
    758 		client->pers.maxHealth = 200;
    759 	} else {
    760 		health = atoi( Info_ValueForKey( userinfo, "handicap" ) );
    761 		client->pers.maxHealth = health;
    762 		if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
    763 			client->pers.maxHealth = 100;
    764 		}
    765 	}
    766 #else
    767 	health = atoi( Info_ValueForKey( userinfo, "handicap" ) );
    768 	client->pers.maxHealth = health;
    769 	if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
    770 		client->pers.maxHealth = 100;
    771 	}
    772 #endif
    773 	client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
    774 
    775 	// set model
    776 	if( g_gametype.integer >= GT_TEAM ) {
    777 		Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) );
    778 		Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) );
    779 	} else {
    780 		Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) );
    781 		Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) );
    782 	}
    783 
    784 	// bots set their team a few frames later
    785 	if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) {
    786 		s = Info_ValueForKey( userinfo, "team" );
    787 		if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) {
    788 			team = TEAM_RED;
    789 		} else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) {
    790 			team = TEAM_BLUE;
    791 		} else {
    792 			// pick the team with the least number of players
    793 			team = PickTeam( clientNum );
    794 		}
    795 	}
    796 	else {
    797 		team = client->sess.sessionTeam;
    798 	}
    799 
    800 /*	NOTE: all client side now
    801 
    802 	// team
    803 	switch( team ) {
    804 	case TEAM_RED:
    805 		ForceClientSkin(client, model, "red");
    806 //		ForceClientSkin(client, headModel, "red");
    807 		break;
    808 	case TEAM_BLUE:
    809 		ForceClientSkin(client, model, "blue");
    810 //		ForceClientSkin(client, headModel, "blue");
    811 		break;
    812 	}
    813 	// don't ever use a default skin in teamplay, it would just waste memory
    814 	// however bots will always join a team but they spawn in as spectator
    815 	if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) {
    816 		ForceClientSkin(client, model, "red");
    817 //		ForceClientSkin(client, headModel, "red");
    818 	}
    819 */
    820 
    821 #ifdef MISSIONPACK
    822 	if (g_gametype.integer >= GT_TEAM) {
    823 		client->pers.teamInfo = qtrue;
    824 	} else {
    825 		s = Info_ValueForKey( userinfo, "teamoverlay" );
    826 		if ( ! *s || atoi( s ) != 0 ) {
    827 			client->pers.teamInfo = qtrue;
    828 		} else {
    829 			client->pers.teamInfo = qfalse;
    830 		}
    831 	}
    832 #else
    833 	// teamInfo
    834 	s = Info_ValueForKey( userinfo, "teamoverlay" );
    835 	if ( ! *s || atoi( s ) != 0 ) {
    836 		client->pers.teamInfo = qtrue;
    837 	} else {
    838 		client->pers.teamInfo = qfalse;
    839 	}
    840 #endif
    841 	/*
    842 	s = Info_ValueForKey( userinfo, "cg_pmove_fixed" );
    843 	if ( !*s || atoi( s ) == 0 ) {
    844 		client->pers.pmoveFixed = qfalse;
    845 	}
    846 	else {
    847 		client->pers.pmoveFixed = qtrue;
    848 	}
    849 	*/
    850 
    851 	// team task (0 = none, 1 = offence, 2 = defence)
    852 	teamTask = atoi(Info_ValueForKey(userinfo, "teamtask"));
    853 	// team Leader (1 = leader, 0 is normal player)
    854 	teamLeader = client->sess.teamLeader;
    855 
    856 	// colors
    857 	strcpy(c1, Info_ValueForKey( userinfo, "color1" ));
    858 	strcpy(c2, Info_ValueForKey( userinfo, "color2" ));
    859 
    860 	strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" ));
    861 	strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" ));
    862 
    863 	// send over a subset of the userinfo keys so other clients can
    864 	// print scoreboards, display models, and play custom sounds
    865 	if ( ent->r.svFlags & SVF_BOT ) {
    866 		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",
    867 			client->pers.netname, team, model, headModel, c1, c2, 
    868 			client->pers.maxHealth, client->sess.wins, client->sess.losses,
    869 			Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader );
    870 	} else {
    871 		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",
    872 			client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, 
    873 			client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader);
    874 	}
    875 
    876 	trap_SetConfigstring( CS_PLAYERS+clientNum, s );
    877 
    878 	// this is not the userinfo, more like the configstring actually
    879 	G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );
    880 }
    881 
    882 
    883 /*
    884 ===========
    885 ClientConnect
    886 
    887 Called when a player begins connecting to the server.
    888 Called again for every map change or tournement restart.
    889 
    890 The session information will be valid after exit.
    891 
    892 Return NULL if the client should be allowed, otherwise return
    893 a string with the reason for denial.
    894 
    895 Otherwise, the client will be sent the current gamestate
    896 and will eventually get to ClientBegin.
    897 
    898 firstTime will be qtrue the very first time a client connects
    899 to the server machine, but qfalse on map changes and tournement
    900 restarts.
    901 ============
    902 */
    903 char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) {
    904 	char		*value;
    905 //	char		*areabits;
    906 	gclient_t	*client;
    907 	char		userinfo[MAX_INFO_STRING];
    908 	gentity_t	*ent;
    909 
    910 	ent = &g_entities[ clientNum ];
    911 
    912 	trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
    913 
    914  	// IP filtering
    915  	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500
    916  	// recommanding PB based IP / GUID banning, the builtin system is pretty limited
    917  	// check to see if they are on the banned IP list
    918 	value = Info_ValueForKey (userinfo, "ip");
    919 	if ( G_FilterPacket( value ) ) {
    920 		return "You are banned from this server.";
    921 	}
    922 
    923   // we don't check password for bots and local client
    924   // NOTE: local client <-> "ip" "localhost"
    925   //   this means this client is not running in our current process
    926 	if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) {
    927 		// check for a password
    928 		value = Info_ValueForKey (userinfo, "password");
    929 		if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) &&
    930 			strcmp( g_password.string, value) != 0) {
    931 			return "Invalid password";
    932 		}
    933 	}
    934 
    935 	// they can connect
    936 	ent->client = level.clients + clientNum;
    937 	client = ent->client;
    938 
    939 //	areabits = client->areabits;
    940 
    941 	memset( client, 0, sizeof(*client) );
    942 
    943 	client->pers.connected = CON_CONNECTING;
    944 
    945 	// read or initialize the session data
    946 	if ( firstTime || level.newSession ) {
    947 		G_InitSessionData( client, userinfo );
    948 	}
    949 	G_ReadSessionData( client );
    950 
    951 	if( isBot ) {
    952 		ent->r.svFlags |= SVF_BOT;
    953 		ent->inuse = qtrue;
    954 		if( !G_BotConnect( clientNum, !firstTime ) ) {
    955 			return "BotConnectfailed";
    956 		}
    957 	}
    958 
    959 	// get and distribute relevent paramters
    960 	G_LogPrintf( "ClientConnect: %i\n", clientNum );
    961 	ClientUserinfoChanged( clientNum );
    962 
    963 	// don't do the "xxx connected" messages if they were caried over from previous level
    964 	if ( firstTime ) {
    965 		trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) );
    966 	}
    967 
    968 	if ( g_gametype.integer >= GT_TEAM &&
    969 		client->sess.sessionTeam != TEAM_SPECTATOR ) {
    970 		BroadcastTeamChange( client, -1 );
    971 	}
    972 
    973 	// count current clients and rank for scoreboard
    974 	CalculateRanks();
    975 
    976 	// for statistics
    977 //	client->areabits = areabits;
    978 //	if ( !client->areabits )
    979 //		client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 );
    980 
    981 	return NULL;
    982 }
    983 
    984 /*
    985 ===========
    986 ClientBegin
    987 
    988 called when a client has finished connecting, and is ready
    989 to be placed into the level.  This will happen every level load,
    990 and on transition between teams, but doesn't happen on respawns
    991 ============
    992 */
    993 void ClientBegin( int clientNum ) {
    994 	gentity_t	*ent;
    995 	gclient_t	*client;
    996 	gentity_t	*tent;
    997 	int			flags;
    998 
    999 	ent = g_entities + clientNum;
   1000 
   1001 	client = level.clients + clientNum;
   1002 
   1003 	if ( ent->r.linked ) {
   1004 		trap_UnlinkEntity( ent );
   1005 	}
   1006 	G_InitGentity( ent );
   1007 	ent->touch = 0;
   1008 	ent->pain = 0;
   1009 	ent->client = client;
   1010 
   1011 	client->pers.connected = CON_CONNECTED;
   1012 	client->pers.enterTime = level.time;
   1013 	client->pers.teamState.state = TEAM_BEGIN;
   1014 
   1015 	// save eflags around this, because changing teams will
   1016 	// cause this to happen with a valid entity, and we
   1017 	// want to make sure the teleport bit is set right
   1018 	// so the viewpoint doesn't interpolate through the
   1019 	// world to the new position
   1020 	flags = client->ps.eFlags;
   1021 	memset( &client->ps, 0, sizeof( client->ps ) );
   1022 	client->ps.eFlags = flags;
   1023 
   1024 	// locate ent at a spawn point
   1025 	ClientSpawn( ent );
   1026 
   1027 	if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
   1028 		// send event
   1029 		tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
   1030 		tent->s.clientNum = ent->s.clientNum;
   1031 
   1032 		if ( g_gametype.integer != GT_TOURNAMENT  ) {
   1033 			trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) );
   1034 		}
   1035 	}
   1036 	G_LogPrintf( "ClientBegin: %i\n", clientNum );
   1037 
   1038 	// count current clients and rank for scoreboard
   1039 	CalculateRanks();
   1040 }
   1041 
   1042 /*
   1043 ===========
   1044 ClientSpawn
   1045 
   1046 Called every time a client is placed fresh in the world:
   1047 after the first ClientBegin, and after each respawn
   1048 Initializes all non-persistant parts of playerState
   1049 ============
   1050 */
   1051 void ClientSpawn(gentity_t *ent) {
   1052 	int		index;
   1053 	vec3_t	spawn_origin, spawn_angles;
   1054 	gclient_t	*client;
   1055 	int		i;
   1056 	clientPersistant_t	saved;
   1057 	clientSession_t		savedSess;
   1058 	int		persistant[MAX_PERSISTANT];
   1059 	gentity_t	*spawnPoint;
   1060 	int		flags;
   1061 	int		savedPing;
   1062 //	char	*savedAreaBits;
   1063 	int		accuracy_hits, accuracy_shots;
   1064 	int		eventSequence;
   1065 	char	userinfo[MAX_INFO_STRING];
   1066 
   1067 	index = ent - g_entities;
   1068 	client = ent->client;
   1069 
   1070 	// find a spawn point
   1071 	// do it before setting health back up, so farthest
   1072 	// ranging doesn't count this client
   1073 	if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
   1074 		spawnPoint = SelectSpectatorSpawnPoint ( 
   1075 						spawn_origin, spawn_angles);
   1076 	} else if (g_gametype.integer >= GT_CTF ) {
   1077 		// all base oriented team games use the CTF spawn points
   1078 		spawnPoint = SelectCTFSpawnPoint ( 
   1079 						client->sess.sessionTeam, 
   1080 						client->pers.teamState.state, 
   1081 						spawn_origin, spawn_angles);
   1082 	} else {
   1083 		do {
   1084 			// the first spawn should be at a good looking spot
   1085 			if ( !client->pers.initialSpawn && client->pers.localClient ) {
   1086 				client->pers.initialSpawn = qtrue;
   1087 				spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles );
   1088 			} else {
   1089 				// don't spawn near existing origin if possible
   1090 				spawnPoint = SelectSpawnPoint ( 
   1091 					client->ps.origin, 
   1092 					spawn_origin, spawn_angles);
   1093 			}
   1094 
   1095 			// Tim needs to prevent bots from spawning at the initial point
   1096 			// on q3dm0...
   1097 			if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) {
   1098 				continue;	// try again
   1099 			}
   1100 			// just to be symetric, we have a nohumans option...
   1101 			if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) {
   1102 				continue;	// try again
   1103 			}
   1104 
   1105 			break;
   1106 
   1107 		} while ( 1 );
   1108 	}
   1109 	client->pers.teamState.state = TEAM_ACTIVE;
   1110 
   1111 	// always clear the kamikaze flag
   1112 	ent->s.eFlags &= ~EF_KAMIKAZE;
   1113 
   1114 	// toggle the teleport bit so the client knows to not lerp
   1115 	// and never clear the voted flag
   1116 	flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED);
   1117 	flags ^= EF_TELEPORT_BIT;
   1118 
   1119 	// clear everything but the persistant data
   1120 
   1121 	saved = client->pers;
   1122 	savedSess = client->sess;
   1123 	savedPing = client->ps.ping;
   1124 //	savedAreaBits = client->areabits;
   1125 	accuracy_hits = client->accuracy_hits;
   1126 	accuracy_shots = client->accuracy_shots;
   1127 	for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
   1128 		persistant[i] = client->ps.persistant[i];
   1129 	}
   1130 	eventSequence = client->ps.eventSequence;
   1131 
   1132 	memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset?
   1133 
   1134 	client->pers = saved;
   1135 	client->sess = savedSess;
   1136 	client->ps.ping = savedPing;
   1137 //	client->areabits = savedAreaBits;
   1138 	client->accuracy_hits = accuracy_hits;
   1139 	client->accuracy_shots = accuracy_shots;
   1140 	client->lastkilled_client = -1;
   1141 
   1142 	for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
   1143 		client->ps.persistant[i] = persistant[i];
   1144 	}
   1145 	client->ps.eventSequence = eventSequence;
   1146 	// increment the spawncount so the client will detect the respawn
   1147 	client->ps.persistant[PERS_SPAWN_COUNT]++;
   1148 	client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
   1149 
   1150 	client->airOutTime = level.time + 12000;
   1151 
   1152 	trap_GetUserinfo( index, userinfo, sizeof(userinfo) );
   1153 	// set max health
   1154 	client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) );
   1155 	if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
   1156 		client->pers.maxHealth = 100;
   1157 	}
   1158 	// clear entity values
   1159 	client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
   1160 	client->ps.eFlags = flags;
   1161 
   1162 	ent->s.groundEntityNum = ENTITYNUM_NONE;
   1163 	ent->client = &level.clients[index];
   1164 	ent->takedamage = qtrue;
   1165 	ent->inuse = qtrue;
   1166 	ent->classname = "player";
   1167 	ent->r.contents = CONTENTS_BODY;
   1168 	ent->clipmask = MASK_PLAYERSOLID;
   1169 	ent->die = player_die;
   1170 	ent->waterlevel = 0;
   1171 	ent->watertype = 0;
   1172 	ent->flags = 0;
   1173 	
   1174 	VectorCopy (playerMins, ent->r.mins);
   1175 	VectorCopy (playerMaxs, ent->r.maxs);
   1176 
   1177 	client->ps.clientNum = index;
   1178 
   1179 	client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
   1180 	if ( g_gametype.integer == GT_TEAM ) {
   1181 		client->ps.ammo[WP_MACHINEGUN] = 50;
   1182 	} else {
   1183 		client->ps.ammo[WP_MACHINEGUN] = 100;
   1184 	}
   1185 
   1186 	client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
   1187 	client->ps.ammo[WP_GAUNTLET] = -1;
   1188 	client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
   1189 
   1190 	// health will count down towards max_health
   1191 	ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25;
   1192 
   1193 	G_SetOrigin( ent, spawn_origin );
   1194 	VectorCopy( spawn_origin, client->ps.origin );
   1195 
   1196 	// the respawned flag will be cleared after the attack and jump keys come up
   1197 	client->ps.pm_flags |= PMF_RESPAWNED;
   1198 
   1199 	trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
   1200 	SetClientViewAngle( ent, spawn_angles );
   1201 
   1202 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
   1203 
   1204 	} else {
   1205 		G_KillBox( ent );
   1206 		trap_LinkEntity (ent);
   1207 
   1208 		// force the base weapon up
   1209 		client->ps.weapon = WP_MACHINEGUN;
   1210 		client->ps.weaponstate = WEAPON_READY;
   1211 
   1212 	}
   1213 
   1214 	// don't allow full run speed for a bit
   1215 	client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
   1216 	client->ps.pm_time = 100;
   1217 
   1218 	client->respawnTime = level.time;
   1219 	client->inactivityTime = level.time + g_inactivity.integer * 1000;
   1220 	client->latched_buttons = 0;
   1221 
   1222 	// set default animations
   1223 	client->ps.torsoAnim = TORSO_STAND;
   1224 	client->ps.legsAnim = LEGS_IDLE;
   1225 
   1226 	if ( level.intermissiontime ) {
   1227 		MoveClientToIntermission( ent );
   1228 	} else {
   1229 		// fire the targets of the spawn point
   1230 		G_UseTargets( spawnPoint, ent );
   1231 
   1232 		// select the highest weapon number available, after any
   1233 		// spawn given items have fired
   1234 		client->ps.weapon = 1;
   1235 		for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
   1236 			if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
   1237 				client->ps.weapon = i;
   1238 				break;
   1239 			}
   1240 		}
   1241 	}
   1242 
   1243 	// run a client frame to drop exactly to the floor,
   1244 	// initialize animations and other things
   1245 	client->ps.commandTime = level.time - 100;
   1246 	ent->client->pers.cmd.serverTime = level.time;
   1247 	ClientThink( ent-g_entities );
   1248 
   1249 	// positively link the client, even if the command times are weird
   1250 	if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
   1251 		BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
   1252 		VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
   1253 		trap_LinkEntity( ent );
   1254 	}
   1255 
   1256 	// run the presend to set anything else
   1257 	ClientEndFrame( ent );
   1258 
   1259 	// clear entity state values
   1260 	BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
   1261 }
   1262 
   1263 
   1264 /*
   1265 ===========
   1266 ClientDisconnect
   1267 
   1268 Called when a player drops from the server.
   1269 Will not be called between levels.
   1270 
   1271 This should NOT be called directly by any game logic,
   1272 call trap_DropClient(), which will call this and do
   1273 server system housekeeping.
   1274 ============
   1275 */
   1276 void ClientDisconnect( int clientNum ) {
   1277 	gentity_t	*ent;
   1278 	gentity_t	*tent;
   1279 	int			i;
   1280 
   1281 	// cleanup if we are kicking a bot that
   1282 	// hasn't spawned yet
   1283 	G_RemoveQueuedBotBegin( clientNum );
   1284 
   1285 	ent = g_entities + clientNum;
   1286 	if ( !ent->client ) {
   1287 		return;
   1288 	}
   1289 
   1290 	// stop any following clients
   1291 	for ( i = 0 ; i < level.maxclients ; i++ ) {
   1292 		if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR
   1293 			&& level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW
   1294 			&& level.clients[i].sess.spectatorClient == clientNum ) {
   1295 			StopFollowing( &g_entities[i] );
   1296 		}
   1297 	}
   1298 
   1299 	// send effect if they were completely connected
   1300 	if ( ent->client->pers.connected == CON_CONNECTED 
   1301 		&& ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
   1302 		tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
   1303 		tent->s.clientNum = ent->s.clientNum;
   1304 
   1305 		// They don't get to take powerups with them!
   1306 		// Especially important for stuff like CTF flags
   1307 		TossClientItems( ent );
   1308 #ifdef MISSIONPACK
   1309 		TossClientPersistantPowerups( ent );
   1310 		if( g_gametype.integer == GT_HARVESTER ) {
   1311 			TossClientCubes( ent );
   1312 		}
   1313 #endif
   1314 
   1315 	}
   1316 
   1317 	G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
   1318 
   1319 	// if we are playing in tourney mode and losing, give a win to the other player
   1320 	if ( (g_gametype.integer == GT_TOURNAMENT )
   1321 		&& !level.intermissiontime
   1322 		&& !level.warmupTime && level.sortedClients[1] == clientNum ) {
   1323 		level.clients[ level.sortedClients[0] ].sess.wins++;
   1324 		ClientUserinfoChanged( level.sortedClients[0] );
   1325 	}
   1326 
   1327 	trap_UnlinkEntity (ent);
   1328 	ent->s.modelindex = 0;
   1329 	ent->inuse = qfalse;
   1330 	ent->classname = "disconnected";
   1331 	ent->client->pers.connected = CON_DISCONNECTED;
   1332 	ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
   1333 	ent->client->sess.sessionTeam = TEAM_FREE;
   1334 
   1335 	trap_SetConfigstring( CS_PLAYERS + clientNum, "");
   1336 
   1337 	CalculateRanks();
   1338 
   1339 	if ( ent->r.svFlags & SVF_BOT ) {
   1340 		BotAIShutdownClient( clientNum, qfalse );
   1341 	}
   1342 }
   1343 
   1344