Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_cmds.c (42694B)


      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 #include "../../ui/menudef.h"			// for the voice chats
     26 
     27 /*
     28 ==================
     29 DeathmatchScoreboardMessage
     30 
     31 ==================
     32 */
     33 void DeathmatchScoreboardMessage( gentity_t *ent ) {
     34 	char		entry[1024];
     35 	char		string[1400];
     36 	int			stringlength;
     37 	int			i, j;
     38 	gclient_t	*cl;
     39 	int			numSorted, scoreFlags, accuracy, perfect;
     40 
     41 	// send the latest information on all clients
     42 	string[0] = 0;
     43 	stringlength = 0;
     44 	scoreFlags = 0;
     45 
     46 	numSorted = level.numConnectedClients;
     47 	
     48 	for (i=0 ; i < numSorted ; i++) {
     49 		int		ping;
     50 
     51 		cl = &level.clients[level.sortedClients[i]];
     52 
     53 		if ( cl->pers.connected == CON_CONNECTING ) {
     54 			ping = -1;
     55 		} else {
     56 			ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
     57 		}
     58 
     59 		if( cl->accuracy_shots ) {
     60 			accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots;
     61 		}
     62 		else {
     63 			accuracy = 0;
     64 		}
     65 		perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0;
     66 
     67 		Com_sprintf (entry, sizeof(entry),
     68 			" %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i],
     69 			cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000,
     70 			scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, 
     71 			cl->ps.persistant[PERS_IMPRESSIVE_COUNT],
     72 			cl->ps.persistant[PERS_EXCELLENT_COUNT],
     73 			cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], 
     74 			cl->ps.persistant[PERS_DEFEND_COUNT], 
     75 			cl->ps.persistant[PERS_ASSIST_COUNT], 
     76 			perfect,
     77 			cl->ps.persistant[PERS_CAPTURES]);
     78 		j = strlen(entry);
     79 		if (stringlength + j > 1024)
     80 			break;
     81 		strcpy (string + stringlength, entry);
     82 		stringlength += j;
     83 	}
     84 
     85 	trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, 
     86 		level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE],
     87 		string ) );
     88 }
     89 
     90 
     91 /*
     92 ==================
     93 Cmd_Score_f
     94 
     95 Request current scoreboard information
     96 ==================
     97 */
     98 void Cmd_Score_f( gentity_t *ent ) {
     99 	DeathmatchScoreboardMessage( ent );
    100 }
    101 
    102 
    103 
    104 /*
    105 ==================
    106 CheatsOk
    107 ==================
    108 */
    109 qboolean	CheatsOk( gentity_t *ent ) {
    110 	if ( !g_cheats.integer ) {
    111 		trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\""));
    112 		return qfalse;
    113 	}
    114 	if ( ent->health <= 0 ) {
    115 		trap_SendServerCommand( ent-g_entities, va("print \"You must be alive to use this command.\n\""));
    116 		return qfalse;
    117 	}
    118 	return qtrue;
    119 }
    120 
    121 
    122 /*
    123 ==================
    124 ConcatArgs
    125 ==================
    126 */
    127 char	*ConcatArgs( int start ) {
    128 	int		i, c, tlen;
    129 	static char	line[MAX_STRING_CHARS];
    130 	int		len;
    131 	char	arg[MAX_STRING_CHARS];
    132 
    133 	len = 0;
    134 	c = trap_Argc();
    135 	for ( i = start ; i < c ; i++ ) {
    136 		trap_Argv( i, arg, sizeof( arg ) );
    137 		tlen = strlen( arg );
    138 		if ( len + tlen >= MAX_STRING_CHARS - 1 ) {
    139 			break;
    140 		}
    141 		memcpy( line + len, arg, tlen );
    142 		len += tlen;
    143 		if ( i != c - 1 ) {
    144 			line[len] = ' ';
    145 			len++;
    146 		}
    147 	}
    148 
    149 	line[len] = 0;
    150 
    151 	return line;
    152 }
    153 
    154 /*
    155 ==================
    156 SanitizeString
    157 
    158 Remove case and control characters
    159 ==================
    160 */
    161 void SanitizeString( char *in, char *out ) {
    162 	while ( *in ) {
    163 		if ( *in == 27 ) {
    164 			in += 2;		// skip color code
    165 			continue;
    166 		}
    167 		if ( *in < 32 ) {
    168 			in++;
    169 			continue;
    170 		}
    171 		*out++ = tolower( *in++ );
    172 	}
    173 
    174 	*out = 0;
    175 }
    176 
    177 /*
    178 ==================
    179 ClientNumberFromString
    180 
    181 Returns a player number for either a number or name string
    182 Returns -1 if invalid
    183 ==================
    184 */
    185 int ClientNumberFromString( gentity_t *to, char *s ) {
    186 	gclient_t	*cl;
    187 	int			idnum;
    188 	char		s2[MAX_STRING_CHARS];
    189 	char		n2[MAX_STRING_CHARS];
    190 
    191 	// numeric values are just slot numbers
    192 	if (s[0] >= '0' && s[0] <= '9') {
    193 		idnum = atoi( s );
    194 		if ( idnum < 0 || idnum >= level.maxclients ) {
    195 			trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum));
    196 			return -1;
    197 		}
    198 
    199 		cl = &level.clients[idnum];
    200 		if ( cl->pers.connected != CON_CONNECTED ) {
    201 			trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum));
    202 			return -1;
    203 		}
    204 		return idnum;
    205 	}
    206 
    207 	// check for a name match
    208 	SanitizeString( s, s2 );
    209 	for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) {
    210 		if ( cl->pers.connected != CON_CONNECTED ) {
    211 			continue;
    212 		}
    213 		SanitizeString( cl->pers.netname, n2 );
    214 		if ( !strcmp( n2, s2 ) ) {
    215 			return idnum;
    216 		}
    217 	}
    218 
    219 	trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s));
    220 	return -1;
    221 }
    222 
    223 /*
    224 ==================
    225 Cmd_Give_f
    226 
    227 Give items to a client
    228 ==================
    229 */
    230 void Cmd_Give_f (gentity_t *ent)
    231 {
    232 	char		*name;
    233 	gitem_t		*it;
    234 	int			i;
    235 	qboolean	give_all;
    236 	gentity_t		*it_ent;
    237 	trace_t		trace;
    238 
    239 	if ( !CheatsOk( ent ) ) {
    240 		return;
    241 	}
    242 
    243 	name = ConcatArgs( 1 );
    244 
    245 	if (Q_stricmp(name, "all") == 0)
    246 		give_all = qtrue;
    247 	else
    248 		give_all = qfalse;
    249 
    250 	if (give_all || Q_stricmp( name, "health") == 0)
    251 	{
    252 		ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
    253 		if (!give_all)
    254 			return;
    255 	}
    256 
    257 	if (give_all || Q_stricmp(name, "weapons") == 0)
    258 	{
    259 		ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - 
    260 			( 1 << WP_GRAPPLING_HOOK ) - ( 1 << WP_NONE );
    261 		if (!give_all)
    262 			return;
    263 	}
    264 
    265 	if (give_all || Q_stricmp(name, "ammo") == 0)
    266 	{
    267 		for ( i = 0 ; i < MAX_WEAPONS ; i++ ) {
    268 			ent->client->ps.ammo[i] = 999;
    269 		}
    270 		if (!give_all)
    271 			return;
    272 	}
    273 
    274 	if (give_all || Q_stricmp(name, "armor") == 0)
    275 	{
    276 		ent->client->ps.stats[STAT_ARMOR] = 200;
    277 
    278 		if (!give_all)
    279 			return;
    280 	}
    281 
    282 	if (Q_stricmp(name, "excellent") == 0) {
    283 		ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
    284 		return;
    285 	}
    286 	if (Q_stricmp(name, "impressive") == 0) {
    287 		ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
    288 		return;
    289 	}
    290 	if (Q_stricmp(name, "gauntletaward") == 0) {
    291 		ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
    292 		return;
    293 	}
    294 	if (Q_stricmp(name, "defend") == 0) {
    295 		ent->client->ps.persistant[PERS_DEFEND_COUNT]++;
    296 		return;
    297 	}
    298 	if (Q_stricmp(name, "assist") == 0) {
    299 		ent->client->ps.persistant[PERS_ASSIST_COUNT]++;
    300 		return;
    301 	}
    302 
    303 	// spawn a specific item right on the player
    304 	if ( !give_all ) {
    305 		it = BG_FindItem (name);
    306 		if (!it) {
    307 			return;
    308 		}
    309 
    310 		it_ent = G_Spawn();
    311 		VectorCopy( ent->r.currentOrigin, it_ent->s.origin );
    312 		it_ent->classname = it->classname;
    313 		G_SpawnItem (it_ent, it);
    314 		FinishSpawningItem(it_ent );
    315 		memset( &trace, 0, sizeof( trace ) );
    316 		Touch_Item (it_ent, ent, &trace);
    317 		if (it_ent->inuse) {
    318 			G_FreeEntity( it_ent );
    319 		}
    320 	}
    321 }
    322 
    323 
    324 /*
    325 ==================
    326 Cmd_God_f
    327 
    328 Sets client to godmode
    329 
    330 argv(0) god
    331 ==================
    332 */
    333 void Cmd_God_f (gentity_t *ent)
    334 {
    335 	char	*msg;
    336 
    337 	if ( !CheatsOk( ent ) ) {
    338 		return;
    339 	}
    340 
    341 	ent->flags ^= FL_GODMODE;
    342 	if (!(ent->flags & FL_GODMODE) )
    343 		msg = "godmode OFF\n";
    344 	else
    345 		msg = "godmode ON\n";
    346 
    347 	trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg));
    348 }
    349 
    350 
    351 /*
    352 ==================
    353 Cmd_Notarget_f
    354 
    355 Sets client to notarget
    356 
    357 argv(0) notarget
    358 ==================
    359 */
    360 void Cmd_Notarget_f( gentity_t *ent ) {
    361 	char	*msg;
    362 
    363 	if ( !CheatsOk( ent ) ) {
    364 		return;
    365 	}
    366 
    367 	ent->flags ^= FL_NOTARGET;
    368 	if (!(ent->flags & FL_NOTARGET) )
    369 		msg = "notarget OFF\n";
    370 	else
    371 		msg = "notarget ON\n";
    372 
    373 	trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg));
    374 }
    375 
    376 
    377 /*
    378 ==================
    379 Cmd_Noclip_f
    380 
    381 argv(0) noclip
    382 ==================
    383 */
    384 void Cmd_Noclip_f( gentity_t *ent ) {
    385 	char	*msg;
    386 
    387 	if ( !CheatsOk( ent ) ) {
    388 		return;
    389 	}
    390 
    391 	if ( ent->client->noclip ) {
    392 		msg = "noclip OFF\n";
    393 	} else {
    394 		msg = "noclip ON\n";
    395 	}
    396 	ent->client->noclip = !ent->client->noclip;
    397 
    398 	trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg));
    399 }
    400 
    401 
    402 /*
    403 ==================
    404 Cmd_LevelShot_f
    405 
    406 This is just to help generate the level pictures
    407 for the menus.  It goes to the intermission immediately
    408 and sends over a command to the client to resize the view,
    409 hide the scoreboard, and take a special screenshot
    410 ==================
    411 */
    412 void Cmd_LevelShot_f( gentity_t *ent ) {
    413 	if ( !CheatsOk( ent ) ) {
    414 		return;
    415 	}
    416 
    417 	// doesn't work in single player
    418 	if ( g_gametype.integer != 0 ) {
    419 		trap_SendServerCommand( ent-g_entities, 
    420 			"print \"Must be in g_gametype 0 for levelshot\n\"" );
    421 		return;
    422 	}
    423 
    424 	BeginIntermission();
    425 	trap_SendServerCommand( ent-g_entities, "clientLevelShot" );
    426 }
    427 
    428 
    429 /*
    430 ==================
    431 Cmd_LevelShot_f
    432 
    433 This is just to help generate the level pictures
    434 for the menus.  It goes to the intermission immediately
    435 and sends over a command to the client to resize the view,
    436 hide the scoreboard, and take a special screenshot
    437 ==================
    438 */
    439 void Cmd_TeamTask_f( gentity_t *ent ) {
    440 	char userinfo[MAX_INFO_STRING];
    441 	char		arg[MAX_TOKEN_CHARS];
    442 	int task;
    443 	int client = ent->client - level.clients;
    444 
    445 	if ( trap_Argc() != 2 ) {
    446 		return;
    447 	}
    448 	trap_Argv( 1, arg, sizeof( arg ) );
    449 	task = atoi( arg );
    450 
    451 	trap_GetUserinfo(client, userinfo, sizeof(userinfo));
    452 	Info_SetValueForKey(userinfo, "teamtask", va("%d", task));
    453 	trap_SetUserinfo(client, userinfo);
    454 	ClientUserinfoChanged(client);
    455 }
    456 
    457 
    458 
    459 /*
    460 =================
    461 Cmd_Kill_f
    462 =================
    463 */
    464 void Cmd_Kill_f( gentity_t *ent ) {
    465 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
    466 		return;
    467 	}
    468 	if (ent->health <= 0) {
    469 		return;
    470 	}
    471 	ent->flags &= ~FL_GODMODE;
    472 	ent->client->ps.stats[STAT_HEALTH] = ent->health = -999;
    473 	player_die (ent, ent, ent, 100000, MOD_SUICIDE);
    474 }
    475 
    476 /*
    477 =================
    478 BroadCastTeamChange
    479 
    480 Let everyone know about a team change
    481 =================
    482 */
    483 void BroadcastTeamChange( gclient_t *client, int oldTeam )
    484 {
    485 	if ( client->sess.sessionTeam == TEAM_RED ) {
    486 		trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"",
    487 			client->pers.netname) );
    488 	} else if ( client->sess.sessionTeam == TEAM_BLUE ) {
    489 		trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"",
    490 		client->pers.netname));
    491 	} else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) {
    492 		trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"",
    493 		client->pers.netname));
    494 	} else if ( client->sess.sessionTeam == TEAM_FREE ) {
    495 		trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"",
    496 		client->pers.netname));
    497 	}
    498 }
    499 
    500 /*
    501 =================
    502 SetTeam
    503 =================
    504 */
    505 void SetTeam( gentity_t *ent, char *s ) {
    506 	int					team, oldTeam;
    507 	gclient_t			*client;
    508 	int					clientNum;
    509 	spectatorState_t	specState;
    510 	int					specClient;
    511 	int					teamLeader;
    512 
    513 	//
    514 	// see what change is requested
    515 	//
    516 	client = ent->client;
    517 
    518 	clientNum = client - level.clients;
    519 	specClient = 0;
    520 	specState = SPECTATOR_NOT;
    521 	if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" )  ) {
    522 		team = TEAM_SPECTATOR;
    523 		specState = SPECTATOR_SCOREBOARD;
    524 	} else if ( !Q_stricmp( s, "follow1" ) ) {
    525 		team = TEAM_SPECTATOR;
    526 		specState = SPECTATOR_FOLLOW;
    527 		specClient = -1;
    528 	} else if ( !Q_stricmp( s, "follow2" ) ) {
    529 		team = TEAM_SPECTATOR;
    530 		specState = SPECTATOR_FOLLOW;
    531 		specClient = -2;
    532 	} else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) {
    533 		team = TEAM_SPECTATOR;
    534 		specState = SPECTATOR_FREE;
    535 	} else if ( g_gametype.integer >= GT_TEAM ) {
    536 		// if running a team game, assign player to one of the teams
    537 		specState = SPECTATOR_NOT;
    538 		if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) {
    539 			team = TEAM_RED;
    540 		} else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) {
    541 			team = TEAM_BLUE;
    542 		} else {
    543 			// pick the team with the least number of players
    544 			team = PickTeam( clientNum );
    545 		}
    546 
    547 		if ( g_teamForceBalance.integer  ) {
    548 			int		counts[TEAM_NUM_TEAMS];
    549 
    550 			counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE );
    551 			counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED );
    552 
    553 			// We allow a spread of two
    554 			if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) {
    555 				trap_SendServerCommand( ent->client->ps.clientNum, 
    556 					"cp \"Red team has too many players.\n\"" );
    557 				return; // ignore the request
    558 			}
    559 			if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) {
    560 				trap_SendServerCommand( ent->client->ps.clientNum, 
    561 					"cp \"Blue team has too many players.\n\"" );
    562 				return; // ignore the request
    563 			}
    564 
    565 			// It's ok, the team we are switching to has less or same number of players
    566 		}
    567 
    568 	} else {
    569 		// force them to spectators if there aren't any spots free
    570 		team = TEAM_FREE;
    571 	}
    572 
    573 	// override decision if limiting the players
    574 	if ( (g_gametype.integer == GT_TOURNAMENT)
    575 		&& level.numNonSpectatorClients >= 2 ) {
    576 		team = TEAM_SPECTATOR;
    577 	} else if ( g_maxGameClients.integer > 0 && 
    578 		level.numNonSpectatorClients >= g_maxGameClients.integer ) {
    579 		team = TEAM_SPECTATOR;
    580 	}
    581 
    582 	//
    583 	// decide if we will allow the change
    584 	//
    585 	oldTeam = client->sess.sessionTeam;
    586 	if ( team == oldTeam && team != TEAM_SPECTATOR ) {
    587 		return;
    588 	}
    589 
    590 	//
    591 	// execute the team change
    592 	//
    593 
    594 	// if the player was dead leave the body
    595 	if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
    596 		CopyToBodyQue(ent);
    597 	}
    598 
    599 	// he starts at 'base'
    600 	client->pers.teamState.state = TEAM_BEGIN;
    601 	if ( oldTeam != TEAM_SPECTATOR ) {
    602 		// Kill him (makes sure he loses flags, etc)
    603 		ent->flags &= ~FL_GODMODE;
    604 		ent->client->ps.stats[STAT_HEALTH] = ent->health = 0;
    605 		player_die (ent, ent, ent, 100000, MOD_SUICIDE);
    606 
    607 	}
    608 	// they go to the end of the line for tournements
    609 	if ( team == TEAM_SPECTATOR ) {
    610 		client->sess.spectatorTime = level.time;
    611 	}
    612 
    613 	client->sess.sessionTeam = team;
    614 	client->sess.spectatorState = specState;
    615 	client->sess.spectatorClient = specClient;
    616 
    617 	client->sess.teamLeader = qfalse;
    618 	if ( team == TEAM_RED || team == TEAM_BLUE ) {
    619 		teamLeader = TeamLeader( team );
    620 		// if there is no team leader or the team leader is a bot and this client is not a bot
    621 		if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) {
    622 			SetLeader( team, clientNum );
    623 		}
    624 	}
    625 	// make sure there is a team leader on the team the player came from
    626 	if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) {
    627 		CheckTeamLeader( oldTeam );
    628 	}
    629 
    630 	BroadcastTeamChange( client, oldTeam );
    631 
    632 	// get and distribute relevent paramters
    633 	ClientUserinfoChanged( clientNum );
    634 
    635 	ClientBegin( clientNum );
    636 }
    637 
    638 /*
    639 =================
    640 StopFollowing
    641 
    642 If the client being followed leaves the game, or you just want to drop
    643 to free floating spectator mode
    644 =================
    645 */
    646 void StopFollowing( gentity_t *ent ) {
    647 	ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;	
    648 	ent->client->sess.sessionTeam = TEAM_SPECTATOR;	
    649 	ent->client->sess.spectatorState = SPECTATOR_FREE;
    650 	ent->client->ps.pm_flags &= ~PMF_FOLLOW;
    651 	ent->r.svFlags &= ~SVF_BOT;
    652 	ent->client->ps.clientNum = ent - g_entities;
    653 }
    654 
    655 /*
    656 =================
    657 Cmd_Team_f
    658 =================
    659 */
    660 void Cmd_Team_f( gentity_t *ent ) {
    661 	int			oldTeam;
    662 	char		s[MAX_TOKEN_CHARS];
    663 
    664 	if ( trap_Argc() != 2 ) {
    665 		oldTeam = ent->client->sess.sessionTeam;
    666 		switch ( oldTeam ) {
    667 		case TEAM_BLUE:
    668 			trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" );
    669 			break;
    670 		case TEAM_RED:
    671 			trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" );
    672 			break;
    673 		case TEAM_FREE:
    674 			trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" );
    675 			break;
    676 		case TEAM_SPECTATOR:
    677 			trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" );
    678 			break;
    679 		}
    680 		return;
    681 	}
    682 
    683 	if ( ent->client->switchTeamTime > level.time ) {
    684 		trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" );
    685 		return;
    686 	}
    687 
    688 	// if they are playing a tournement game, count as a loss
    689 	if ( (g_gametype.integer == GT_TOURNAMENT )
    690 		&& ent->client->sess.sessionTeam == TEAM_FREE ) {
    691 		ent->client->sess.losses++;
    692 	}
    693 
    694 	trap_Argv( 1, s, sizeof( s ) );
    695 
    696 	SetTeam( ent, s );
    697 
    698 	ent->client->switchTeamTime = level.time + 5000;
    699 }
    700 
    701 
    702 /*
    703 =================
    704 Cmd_Follow_f
    705 =================
    706 */
    707 void Cmd_Follow_f( gentity_t *ent ) {
    708 	int		i;
    709 	char	arg[MAX_TOKEN_CHARS];
    710 
    711 	if ( trap_Argc() != 2 ) {
    712 		if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
    713 			StopFollowing( ent );
    714 		}
    715 		return;
    716 	}
    717 
    718 	trap_Argv( 1, arg, sizeof( arg ) );
    719 	i = ClientNumberFromString( ent, arg );
    720 	if ( i == -1 ) {
    721 		return;
    722 	}
    723 
    724 	// can't follow self
    725 	if ( &level.clients[ i ] == ent->client ) {
    726 		return;
    727 	}
    728 
    729 	// can't follow another spectator
    730 	if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) {
    731 		return;
    732 	}
    733 
    734 	// if they are playing a tournement game, count as a loss
    735 	if ( (g_gametype.integer == GT_TOURNAMENT )
    736 		&& ent->client->sess.sessionTeam == TEAM_FREE ) {
    737 		ent->client->sess.losses++;
    738 	}
    739 
    740 	// first set them to spectator
    741 	if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
    742 		SetTeam( ent, "spectator" );
    743 	}
    744 
    745 	ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
    746 	ent->client->sess.spectatorClient = i;
    747 }
    748 
    749 /*
    750 =================
    751 Cmd_FollowCycle_f
    752 =================
    753 */
    754 void Cmd_FollowCycle_f( gentity_t *ent, int dir ) {
    755 	int		clientnum;
    756 	int		original;
    757 
    758 	// if they are playing a tournement game, count as a loss
    759 	if ( (g_gametype.integer == GT_TOURNAMENT )
    760 		&& ent->client->sess.sessionTeam == TEAM_FREE ) {
    761 		ent->client->sess.losses++;
    762 	}
    763 	// first set them to spectator
    764 	if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) {
    765 		SetTeam( ent, "spectator" );
    766 	}
    767 
    768 	if ( dir != 1 && dir != -1 ) {
    769 		G_Error( "Cmd_FollowCycle_f: bad dir %i", dir );
    770 	}
    771 
    772 	clientnum = ent->client->sess.spectatorClient;
    773 	original = clientnum;
    774 	do {
    775 		clientnum += dir;
    776 		if ( clientnum >= level.maxclients ) {
    777 			clientnum = 0;
    778 		}
    779 		if ( clientnum < 0 ) {
    780 			clientnum = level.maxclients - 1;
    781 		}
    782 
    783 		// can only follow connected clients
    784 		if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) {
    785 			continue;
    786 		}
    787 
    788 		// can't follow another spectator
    789 		if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) {
    790 			continue;
    791 		}
    792 
    793 		// this is good, we can use it
    794 		ent->client->sess.spectatorClient = clientnum;
    795 		ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
    796 		return;
    797 	} while ( clientnum != original );
    798 
    799 	// leave it where it was
    800 }
    801 
    802 
    803 /*
    804 ==================
    805 G_Say
    806 ==================
    807 */
    808 
    809 static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ) {
    810 	if (!other) {
    811 		return;
    812 	}
    813 	if (!other->inuse) {
    814 		return;
    815 	}
    816 	if (!other->client) {
    817 		return;
    818 	}
    819 	if ( other->client->pers.connected != CON_CONNECTED ) {
    820 		return;
    821 	}
    822 	if ( mode == SAY_TEAM  && !OnSameTeam(ent, other) ) {
    823 		return;
    824 	}
    825 	// no chatting to players in tournements
    826 	if ( (g_gametype.integer == GT_TOURNAMENT )
    827 		&& other->client->sess.sessionTeam == TEAM_FREE
    828 		&& ent->client->sess.sessionTeam != TEAM_FREE ) {
    829 		return;
    830 	}
    831 
    832 	trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", 
    833 		mode == SAY_TEAM ? "tchat" : "chat",
    834 		name, Q_COLOR_ESCAPE, color, message));
    835 }
    836 
    837 #define EC		"\x19"
    838 
    839 void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) {
    840 	int			j;
    841 	gentity_t	*other;
    842 	int			color;
    843 	char		name[64];
    844 	// don't let text be too long for malicious reasons
    845 	char		text[MAX_SAY_TEXT];
    846 	char		location[64];
    847 
    848 	if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) {
    849 		mode = SAY_ALL;
    850 	}
    851 
    852 	switch ( mode ) {
    853 	default:
    854 	case SAY_ALL:
    855 		G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText );
    856 		Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
    857 		color = COLOR_GREEN;
    858 		break;
    859 	case SAY_TEAM:
    860 		G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText );
    861 		if (Team_GetLocationMsg(ent, location, sizeof(location)))
    862 			Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC") (%s)"EC": ", 
    863 				ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location);
    864 		else
    865 			Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", 
    866 				ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
    867 		color = COLOR_CYAN;
    868 		break;
    869 	case SAY_TELL:
    870 		if (target && g_gametype.integer >= GT_TEAM &&
    871 			target->client->sess.sessionTeam == ent->client->sess.sessionTeam &&
    872 			Team_GetLocationMsg(ent, location, sizeof(location)))
    873 			Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"] (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location );
    874 		else
    875 			Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
    876 		color = COLOR_MAGENTA;
    877 		break;
    878 	}
    879 
    880 	Q_strncpyz( text, chatText, sizeof(text) );
    881 
    882 	if ( target ) {
    883 		G_SayTo( ent, target, mode, color, name, text );
    884 		return;
    885 	}
    886 
    887 	// echo the text to the console
    888 	if ( g_dedicated.integer ) {
    889 		G_Printf( "%s%s\n", name, text);
    890 	}
    891 
    892 	// send it to all the apropriate clients
    893 	for (j = 0; j < level.maxclients; j++) {
    894 		other = &g_entities[j];
    895 		G_SayTo( ent, other, mode, color, name, text );
    896 	}
    897 }
    898 
    899 
    900 /*
    901 ==================
    902 Cmd_Say_f
    903 ==================
    904 */
    905 static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) {
    906 	char		*p;
    907 
    908 	if ( trap_Argc () < 2 && !arg0 ) {
    909 		return;
    910 	}
    911 
    912 	if (arg0)
    913 	{
    914 		p = ConcatArgs( 0 );
    915 	}
    916 	else
    917 	{
    918 		p = ConcatArgs( 1 );
    919 	}
    920 
    921 	G_Say( ent, NULL, mode, p );
    922 }
    923 
    924 /*
    925 ==================
    926 Cmd_Tell_f
    927 ==================
    928 */
    929 static void Cmd_Tell_f( gentity_t *ent ) {
    930 	int			targetNum;
    931 	gentity_t	*target;
    932 	char		*p;
    933 	char		arg[MAX_TOKEN_CHARS];
    934 
    935 	if ( trap_Argc () < 2 ) {
    936 		return;
    937 	}
    938 
    939 	trap_Argv( 1, arg, sizeof( arg ) );
    940 	targetNum = atoi( arg );
    941 	if ( targetNum < 0 || targetNum >= level.maxclients ) {
    942 		return;
    943 	}
    944 
    945 	target = &g_entities[targetNum];
    946 	if ( !target || !target->inuse || !target->client ) {
    947 		return;
    948 	}
    949 
    950 	p = ConcatArgs( 2 );
    951 
    952 	G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p );
    953 	G_Say( ent, target, SAY_TELL, p );
    954 	// don't tell to the player self if it was already directed to this player
    955 	// also don't send the chat back to a bot
    956 	if ( ent != target && !(ent->r.svFlags & SVF_BOT)) {
    957 		G_Say( ent, ent, SAY_TELL, p );
    958 	}
    959 }
    960 
    961 
    962 static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *id, qboolean voiceonly ) {
    963 	int color;
    964 	char *cmd;
    965 
    966 	if (!other) {
    967 		return;
    968 	}
    969 	if (!other->inuse) {
    970 		return;
    971 	}
    972 	if (!other->client) {
    973 		return;
    974 	}
    975 	if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) {
    976 		return;
    977 	}
    978 	// no chatting to players in tournements
    979 	if ( (g_gametype.integer == GT_TOURNAMENT )) {
    980 		return;
    981 	}
    982 
    983 	if (mode == SAY_TEAM) {
    984 		color = COLOR_CYAN;
    985 		cmd = "vtchat";
    986 	}
    987 	else if (mode == SAY_TELL) {
    988 		color = COLOR_MAGENTA;
    989 		cmd = "vtell";
    990 	}
    991 	else {
    992 		color = COLOR_GREEN;
    993 		cmd = "vchat";
    994 	}
    995 
    996 	trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id));
    997 }
    998 
    999 void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) {
   1000 	int			j;
   1001 	gentity_t	*other;
   1002 
   1003 	if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) {
   1004 		mode = SAY_ALL;
   1005 	}
   1006 
   1007 	if ( target ) {
   1008 		G_VoiceTo( ent, target, mode, id, voiceonly );
   1009 		return;
   1010 	}
   1011 
   1012 	// echo the text to the console
   1013 	if ( g_dedicated.integer ) {
   1014 		G_Printf( "voice: %s %s\n", ent->client->pers.netname, id);
   1015 	}
   1016 
   1017 	// send it to all the apropriate clients
   1018 	for (j = 0; j < level.maxclients; j++) {
   1019 		other = &g_entities[j];
   1020 		G_VoiceTo( ent, other, mode, id, voiceonly );
   1021 	}
   1022 }
   1023 
   1024 /*
   1025 ==================
   1026 Cmd_Voice_f
   1027 ==================
   1028 */
   1029 static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) {
   1030 	char		*p;
   1031 
   1032 	if ( trap_Argc () < 2 && !arg0 ) {
   1033 		return;
   1034 	}
   1035 
   1036 	if (arg0)
   1037 	{
   1038 		p = ConcatArgs( 0 );
   1039 	}
   1040 	else
   1041 	{
   1042 		p = ConcatArgs( 1 );
   1043 	}
   1044 
   1045 	G_Voice( ent, NULL, mode, p, voiceonly );
   1046 }
   1047 
   1048 /*
   1049 ==================
   1050 Cmd_VoiceTell_f
   1051 ==================
   1052 */
   1053 static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) {
   1054 	int			targetNum;
   1055 	gentity_t	*target;
   1056 	char		*id;
   1057 	char		arg[MAX_TOKEN_CHARS];
   1058 
   1059 	if ( trap_Argc () < 2 ) {
   1060 		return;
   1061 	}
   1062 
   1063 	trap_Argv( 1, arg, sizeof( arg ) );
   1064 	targetNum = atoi( arg );
   1065 	if ( targetNum < 0 || targetNum >= level.maxclients ) {
   1066 		return;
   1067 	}
   1068 
   1069 	target = &g_entities[targetNum];
   1070 	if ( !target || !target->inuse || !target->client ) {
   1071 		return;
   1072 	}
   1073 
   1074 	id = ConcatArgs( 2 );
   1075 
   1076 	G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id );
   1077 	G_Voice( ent, target, SAY_TELL, id, voiceonly );
   1078 	// don't tell to the player self if it was already directed to this player
   1079 	// also don't send the chat back to a bot
   1080 	if ( ent != target && !(ent->r.svFlags & SVF_BOT)) {
   1081 		G_Voice( ent, ent, SAY_TELL, id, voiceonly );
   1082 	}
   1083 }
   1084 
   1085 
   1086 /*
   1087 ==================
   1088 Cmd_VoiceTaunt_f
   1089 ==================
   1090 */
   1091 static void Cmd_VoiceTaunt_f( gentity_t *ent ) {
   1092 	gentity_t *who;
   1093 	int i;
   1094 
   1095 	if (!ent->client) {
   1096 		return;
   1097 	}
   1098 
   1099 	// insult someone who just killed you
   1100 	if (ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) {
   1101 		// i am a dead corpse
   1102 		if (!(ent->enemy->r.svFlags & SVF_BOT)) {
   1103 			G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse );
   1104 		}
   1105 		if (!(ent->r.svFlags & SVF_BOT)) {
   1106 			G_Voice( ent, ent,        SAY_TELL, VOICECHAT_DEATHINSULT, qfalse );
   1107 		}
   1108 		ent->enemy = NULL;
   1109 		return;
   1110 	}
   1111 	// insult someone you just killed
   1112 	if (ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) {
   1113 		who = g_entities + ent->client->lastkilled_client;
   1114 		if (who->client) {
   1115 			// who is the person I just killed
   1116 			if (who->client->lasthurt_mod == MOD_GAUNTLET) {
   1117 				if (!(who->r.svFlags & SVF_BOT)) {
   1118 					G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse );	// and I killed them with a gauntlet
   1119 				}
   1120 				if (!(ent->r.svFlags & SVF_BOT)) {
   1121 					G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse );
   1122 				}
   1123 			} else {
   1124 				if (!(who->r.svFlags & SVF_BOT)) {
   1125 					G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse );	// and I killed them with something else
   1126 				}
   1127 				if (!(ent->r.svFlags & SVF_BOT)) {
   1128 					G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse );
   1129 				}
   1130 			}
   1131 			ent->client->lastkilled_client = -1;
   1132 			return;
   1133 		}
   1134 	}
   1135 
   1136 	if (g_gametype.integer >= GT_TEAM) {
   1137 		// praise a team mate who just got a reward
   1138 		for(i = 0; i < MAX_CLIENTS; i++) {
   1139 			who = g_entities + i;
   1140 			if (who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) {
   1141 				if (who->client->rewardTime > level.time) {
   1142 					if (!(who->r.svFlags & SVF_BOT)) {
   1143 						G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse );
   1144 					}
   1145 					if (!(ent->r.svFlags & SVF_BOT)) {
   1146 						G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse );
   1147 					}
   1148 					return;
   1149 				}
   1150 			}
   1151 		}
   1152 	}
   1153 
   1154 	// just say something
   1155 	G_Voice( ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse );
   1156 }
   1157 
   1158 
   1159 
   1160 static char	*gc_orders[] = {
   1161 	"hold your position",
   1162 	"hold this position",
   1163 	"come here",
   1164 	"cover me",
   1165 	"guard location",
   1166 	"search and destroy",
   1167 	"report"
   1168 };
   1169 
   1170 void Cmd_GameCommand_f( gentity_t *ent ) {
   1171 	int		player;
   1172 	int		order;
   1173 	char	str[MAX_TOKEN_CHARS];
   1174 
   1175 	trap_Argv( 1, str, sizeof( str ) );
   1176 	player = atoi( str );
   1177 	trap_Argv( 2, str, sizeof( str ) );
   1178 	order = atoi( str );
   1179 
   1180 	if ( player < 0 || player >= MAX_CLIENTS ) {
   1181 		return;
   1182 	}
   1183 	if ( order < 0 || order > sizeof(gc_orders)/sizeof(char *) ) {
   1184 		return;
   1185 	}
   1186 	G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] );
   1187 	G_Say( ent, ent, SAY_TELL, gc_orders[order] );
   1188 }
   1189 
   1190 /*
   1191 ==================
   1192 Cmd_Where_f
   1193 ==================
   1194 */
   1195 void Cmd_Where_f( gentity_t *ent ) {
   1196 	trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
   1197 }
   1198 
   1199 static const char *gameNames[] = {
   1200 	"Free For All",
   1201 	"Tournament",
   1202 	"Single Player",
   1203 	"Team Deathmatch",
   1204 	"Capture the Flag",
   1205 	"One Flag CTF",
   1206 	"Overload",
   1207 	"Harvester"
   1208 };
   1209 
   1210 /*
   1211 ==================
   1212 Cmd_CallVote_f
   1213 ==================
   1214 */
   1215 void Cmd_CallVote_f( gentity_t *ent ) {
   1216 	int		i;
   1217 	char	arg1[MAX_STRING_TOKENS];
   1218 	char	arg2[MAX_STRING_TOKENS];
   1219 
   1220 	if ( !g_allowVote.integer ) {
   1221 		trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" );
   1222 		return;
   1223 	}
   1224 
   1225 	if ( level.voteTime ) {
   1226 		trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" );
   1227 		return;
   1228 	}
   1229 	if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) {
   1230 		trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" );
   1231 		return;
   1232 	}
   1233 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
   1234 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" );
   1235 		return;
   1236 	}
   1237 
   1238 	// make sure it is a valid command to vote on
   1239 	trap_Argv( 1, arg1, sizeof( arg1 ) );
   1240 	trap_Argv( 2, arg2, sizeof( arg2 ) );
   1241 
   1242 	if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) {
   1243 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
   1244 		return;
   1245 	}
   1246 
   1247 	if ( !Q_stricmp( arg1, "map_restart" ) ) {
   1248 	} else if ( !Q_stricmp( arg1, "nextmap" ) ) {
   1249 	} else if ( !Q_stricmp( arg1, "map" ) ) {
   1250 	} else if ( !Q_stricmp( arg1, "g_gametype" ) ) {
   1251 	} else if ( !Q_stricmp( arg1, "kick" ) ) {
   1252 	} else if ( !Q_stricmp( arg1, "clientkick" ) ) {
   1253 	} else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) {
   1254 	} else if ( !Q_stricmp( arg1, "timelimit" ) ) {
   1255 	} else if ( !Q_stricmp( arg1, "fraglimit" ) ) {
   1256 	} else {
   1257 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
   1258 		trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map <mapname>, g_gametype <n>, kick <player>, clientkick <clientnum>, g_doWarmup, timelimit <time>, fraglimit <frags>.\n\"" );
   1259 		return;
   1260 	}
   1261 
   1262 	// if there is still a vote to be executed
   1263 	if ( level.voteExecuteTime ) {
   1264 		level.voteExecuteTime = 0;
   1265 		trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) );
   1266 	}
   1267 
   1268 	// special case for g_gametype, check for bad values
   1269 	if ( !Q_stricmp( arg1, "g_gametype" ) ) {
   1270 		i = atoi( arg2 );
   1271 		if( i == GT_SINGLE_PLAYER || i < GT_FFA || i >= GT_MAX_GAME_TYPE) {
   1272 			trap_SendServerCommand( ent-g_entities, "print \"Invalid gametype.\n\"" );
   1273 			return;
   1274 		}
   1275 
   1276 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %d", arg1, i );
   1277 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, gameNames[i] );
   1278 	} else if ( !Q_stricmp( arg1, "map" ) ) {
   1279 		// special case for map changes, we want to reset the nextmap setting
   1280 		// this allows a player to change maps, but not upset the map rotation
   1281 		char	s[MAX_STRING_CHARS];
   1282 
   1283 		trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof(s) );
   1284 		if (*s) {
   1285 			Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s; set nextmap \"%s\"", arg1, arg2, s );
   1286 		} else {
   1287 			Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 );
   1288 		}
   1289 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
   1290 	} else if ( !Q_stricmp( arg1, "nextmap" ) ) {
   1291 		char	s[MAX_STRING_CHARS];
   1292 
   1293 		trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof(s) );
   1294 		if (!*s) {
   1295 			trap_SendServerCommand( ent-g_entities, "print \"nextmap not set.\n\"" );
   1296 			return;
   1297 		}
   1298 		Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap");
   1299 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
   1300 	} else {
   1301 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 );
   1302 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
   1303 	}
   1304 
   1305 	trap_SendServerCommand( -1, va("print \"%s called a vote.\n\"", ent->client->pers.netname ) );
   1306 
   1307 	// start the voting, the caller autoamtically votes yes
   1308 	level.voteTime = level.time;
   1309 	level.voteYes = 1;
   1310 	level.voteNo = 0;
   1311 
   1312 	for ( i = 0 ; i < level.maxclients ; i++ ) {
   1313 		level.clients[i].ps.eFlags &= ~EF_VOTED;
   1314 	}
   1315 	ent->client->ps.eFlags |= EF_VOTED;
   1316 
   1317 	trap_SetConfigstring( CS_VOTE_TIME, va("%i", level.voteTime ) );
   1318 	trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString );	
   1319 	trap_SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) );
   1320 	trap_SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) );	
   1321 }
   1322 
   1323 /*
   1324 ==================
   1325 Cmd_Vote_f
   1326 ==================
   1327 */
   1328 void Cmd_Vote_f( gentity_t *ent ) {
   1329 	char		msg[64];
   1330 
   1331 	if ( !level.voteTime ) {
   1332 		trap_SendServerCommand( ent-g_entities, "print \"No vote in progress.\n\"" );
   1333 		return;
   1334 	}
   1335 	if ( ent->client->ps.eFlags & EF_VOTED ) {
   1336 		trap_SendServerCommand( ent-g_entities, "print \"Vote already cast.\n\"" );
   1337 		return;
   1338 	}
   1339 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
   1340 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to vote as spectator.\n\"" );
   1341 		return;
   1342 	}
   1343 
   1344 	trap_SendServerCommand( ent-g_entities, "print \"Vote cast.\n\"" );
   1345 
   1346 	ent->client->ps.eFlags |= EF_VOTED;
   1347 
   1348 	trap_Argv( 1, msg, sizeof( msg ) );
   1349 
   1350 	if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) {
   1351 		level.voteYes++;
   1352 		trap_SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) );
   1353 	} else {
   1354 		level.voteNo++;
   1355 		trap_SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) );	
   1356 	}
   1357 
   1358 	// a majority will be determined in CheckVote, which will also account
   1359 	// for players entering or leaving
   1360 }
   1361 
   1362 /*
   1363 ==================
   1364 Cmd_CallTeamVote_f
   1365 ==================
   1366 */
   1367 void Cmd_CallTeamVote_f( gentity_t *ent ) {
   1368 	int		i, team, cs_offset;
   1369 	char	arg1[MAX_STRING_TOKENS];
   1370 	char	arg2[MAX_STRING_TOKENS];
   1371 
   1372 	team = ent->client->sess.sessionTeam;
   1373 	if ( team == TEAM_RED )
   1374 		cs_offset = 0;
   1375 	else if ( team == TEAM_BLUE )
   1376 		cs_offset = 1;
   1377 	else
   1378 		return;
   1379 
   1380 	if ( !g_allowVote.integer ) {
   1381 		trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" );
   1382 		return;
   1383 	}
   1384 
   1385 	if ( level.teamVoteTime[cs_offset] ) {
   1386 		trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress.\n\"" );
   1387 		return;
   1388 	}
   1389 	if ( ent->client->pers.teamVoteCount >= MAX_VOTE_COUNT ) {
   1390 		trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of team votes.\n\"" );
   1391 		return;
   1392 	}
   1393 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
   1394 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" );
   1395 		return;
   1396 	}
   1397 
   1398 	// make sure it is a valid command to vote on
   1399 	trap_Argv( 1, arg1, sizeof( arg1 ) );
   1400 	arg2[0] = '\0';
   1401 	for ( i = 2; i < trap_Argc(); i++ ) {
   1402 		if (i > 2)
   1403 			strcat(arg2, " ");
   1404 		trap_Argv( i, &arg2[strlen(arg2)], sizeof( arg2 ) - strlen(arg2) );
   1405 	}
   1406 
   1407 	if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) {
   1408 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
   1409 		return;
   1410 	}
   1411 
   1412 	if ( !Q_stricmp( arg1, "leader" ) ) {
   1413 		char netname[MAX_NETNAME], leader[MAX_NETNAME];
   1414 
   1415 		if ( !arg2[0] ) {
   1416 			i = ent->client->ps.clientNum;
   1417 		}
   1418 		else {
   1419 			// numeric values are just slot numbers
   1420 			for (i = 0; i < 3; i++) {
   1421 				if ( !arg2[i] || arg2[i] < '0' || arg2[i] > '9' )
   1422 					break;
   1423 			}
   1424 			if ( i >= 3 || !arg2[i]) {
   1425 				i = atoi( arg2 );
   1426 				if ( i < 0 || i >= level.maxclients ) {
   1427 					trap_SendServerCommand( ent-g_entities, va("print \"Bad client slot: %i\n\"", i) );
   1428 					return;
   1429 				}
   1430 
   1431 				if ( !g_entities[i].inuse ) {
   1432 					trap_SendServerCommand( ent-g_entities, va("print \"Client %i is not active\n\"", i) );
   1433 					return;
   1434 				}
   1435 			}
   1436 			else {
   1437 				Q_strncpyz(leader, arg2, sizeof(leader));
   1438 				Q_CleanStr(leader);
   1439 				for ( i = 0 ; i < level.maxclients ; i++ ) {
   1440 					if ( level.clients[i].pers.connected == CON_DISCONNECTED )
   1441 						continue;
   1442 					if (level.clients[i].sess.sessionTeam != team)
   1443 						continue;
   1444 					Q_strncpyz(netname, level.clients[i].pers.netname, sizeof(netname));
   1445 					Q_CleanStr(netname);
   1446 					if ( !Q_stricmp(netname, leader) ) {
   1447 						break;
   1448 					}
   1449 				}
   1450 				if ( i >= level.maxclients ) {
   1451 					trap_SendServerCommand( ent-g_entities, va("print \"%s is not a valid player on your team.\n\"", arg2) );
   1452 					return;
   1453 				}
   1454 			}
   1455 		}
   1456 		Com_sprintf(arg2, sizeof(arg2), "%d", i);
   1457 	} else {
   1458 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
   1459 		trap_SendServerCommand( ent-g_entities, "print \"Team vote commands are: leader <player>.\n\"" );
   1460 		return;
   1461 	}
   1462 
   1463 	Com_sprintf( level.teamVoteString[cs_offset], sizeof( level.teamVoteString[cs_offset] ), "%s %s", arg1, arg2 );
   1464 
   1465 	for ( i = 0 ; i < level.maxclients ; i++ ) {
   1466 		if ( level.clients[i].pers.connected == CON_DISCONNECTED )
   1467 			continue;
   1468 		if (level.clients[i].sess.sessionTeam == team)
   1469 			trap_SendServerCommand( i, va("print \"%s called a team vote.\n\"", ent->client->pers.netname ) );
   1470 	}
   1471 
   1472 	// start the voting, the caller autoamtically votes yes
   1473 	level.teamVoteTime[cs_offset] = level.time;
   1474 	level.teamVoteYes[cs_offset] = 1;
   1475 	level.teamVoteNo[cs_offset] = 0;
   1476 
   1477 	for ( i = 0 ; i < level.maxclients ; i++ ) {
   1478 		if (level.clients[i].sess.sessionTeam == team)
   1479 			level.clients[i].ps.eFlags &= ~EF_TEAMVOTED;
   1480 	}
   1481 	ent->client->ps.eFlags |= EF_TEAMVOTED;
   1482 
   1483 	trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va("%i", level.teamVoteTime[cs_offset] ) );
   1484 	trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteString[cs_offset] );
   1485 	trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) );
   1486 	trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) );
   1487 }
   1488 
   1489 /*
   1490 ==================
   1491 Cmd_TeamVote_f
   1492 ==================
   1493 */
   1494 void Cmd_TeamVote_f( gentity_t *ent ) {
   1495 	int			team, cs_offset;
   1496 	char		msg[64];
   1497 
   1498 	team = ent->client->sess.sessionTeam;
   1499 	if ( team == TEAM_RED )
   1500 		cs_offset = 0;
   1501 	else if ( team == TEAM_BLUE )
   1502 		cs_offset = 1;
   1503 	else
   1504 		return;
   1505 
   1506 	if ( !level.teamVoteTime[cs_offset] ) {
   1507 		trap_SendServerCommand( ent-g_entities, "print \"No team vote in progress.\n\"" );
   1508 		return;
   1509 	}
   1510 	if ( ent->client->ps.eFlags & EF_TEAMVOTED ) {
   1511 		trap_SendServerCommand( ent-g_entities, "print \"Team vote already cast.\n\"" );
   1512 		return;
   1513 	}
   1514 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
   1515 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to vote as spectator.\n\"" );
   1516 		return;
   1517 	}
   1518 
   1519 	trap_SendServerCommand( ent-g_entities, "print \"Team vote cast.\n\"" );
   1520 
   1521 	ent->client->ps.eFlags |= EF_TEAMVOTED;
   1522 
   1523 	trap_Argv( 1, msg, sizeof( msg ) );
   1524 
   1525 	if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) {
   1526 		level.teamVoteYes[cs_offset]++;
   1527 		trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) );
   1528 	} else {
   1529 		level.teamVoteNo[cs_offset]++;
   1530 		trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) );	
   1531 	}
   1532 
   1533 	// a majority will be determined in TeamCheckVote, which will also account
   1534 	// for players entering or leaving
   1535 }
   1536 
   1537 
   1538 /*
   1539 =================
   1540 Cmd_SetViewpos_f
   1541 =================
   1542 */
   1543 void Cmd_SetViewpos_f( gentity_t *ent ) {
   1544 	vec3_t		origin, angles;
   1545 	char		buffer[MAX_TOKEN_CHARS];
   1546 	int			i;
   1547 
   1548 	if ( !g_cheats.integer ) {
   1549 		trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\""));
   1550 		return;
   1551 	}
   1552 	if ( trap_Argc() != 5 ) {
   1553 		trap_SendServerCommand( ent-g_entities, va("print \"usage: setviewpos x y z yaw\n\""));
   1554 		return;
   1555 	}
   1556 
   1557 	VectorClear( angles );
   1558 	for ( i = 0 ; i < 3 ; i++ ) {
   1559 		trap_Argv( i + 1, buffer, sizeof( buffer ) );
   1560 		origin[i] = atof( buffer );
   1561 	}
   1562 
   1563 	trap_Argv( 4, buffer, sizeof( buffer ) );
   1564 	angles[YAW] = atof( buffer );
   1565 
   1566 	TeleportPlayer( ent, origin, angles );
   1567 }
   1568 
   1569 
   1570 
   1571 /*
   1572 =================
   1573 Cmd_Stats_f
   1574 =================
   1575 */
   1576 void Cmd_Stats_f( gentity_t *ent ) {
   1577 /*
   1578 	int max, n, i;
   1579 
   1580 	max = trap_AAS_PointReachabilityAreaIndex( NULL );
   1581 
   1582 	n = 0;
   1583 	for ( i = 0; i < max; i++ ) {
   1584 		if ( ent->client->areabits[i >> 3] & (1 << (i & 7)) )
   1585 			n++;
   1586 	}
   1587 
   1588 	//trap_SendServerCommand( ent-g_entities, va("print \"visited %d of %d areas\n\"", n, max));
   1589 	trap_SendServerCommand( ent-g_entities, va("print \"%d%% level coverage\n\"", n * 100 / max));
   1590 */
   1591 }
   1592 
   1593 /*
   1594 =================
   1595 ClientCommand
   1596 =================
   1597 */
   1598 void ClientCommand( int clientNum ) {
   1599 	gentity_t *ent;
   1600 	char	cmd[MAX_TOKEN_CHARS];
   1601 
   1602 	ent = g_entities + clientNum;
   1603 	if ( !ent->client ) {
   1604 		return;		// not fully in game yet
   1605 	}
   1606 
   1607 
   1608 	trap_Argv( 0, cmd, sizeof( cmd ) );
   1609 
   1610 	if (Q_stricmp (cmd, "say") == 0) {
   1611 		Cmd_Say_f (ent, SAY_ALL, qfalse);
   1612 		return;
   1613 	}
   1614 	if (Q_stricmp (cmd, "say_team") == 0) {
   1615 		Cmd_Say_f (ent, SAY_TEAM, qfalse);
   1616 		return;
   1617 	}
   1618 	if (Q_stricmp (cmd, "tell") == 0) {
   1619 		Cmd_Tell_f ( ent );
   1620 		return;
   1621 	}
   1622 	if (Q_stricmp (cmd, "vsay") == 0) {
   1623 		Cmd_Voice_f (ent, SAY_ALL, qfalse, qfalse);
   1624 		return;
   1625 	}
   1626 	if (Q_stricmp (cmd, "vsay_team") == 0) {
   1627 		Cmd_Voice_f (ent, SAY_TEAM, qfalse, qfalse);
   1628 		return;
   1629 	}
   1630 	if (Q_stricmp (cmd, "vtell") == 0) {
   1631 		Cmd_VoiceTell_f ( ent, qfalse );
   1632 		return;
   1633 	}
   1634 	if (Q_stricmp (cmd, "vosay") == 0) {
   1635 		Cmd_Voice_f (ent, SAY_ALL, qfalse, qtrue);
   1636 		return;
   1637 	}
   1638 	if (Q_stricmp (cmd, "vosay_team") == 0) {
   1639 		Cmd_Voice_f (ent, SAY_TEAM, qfalse, qtrue);
   1640 		return;
   1641 	}
   1642 	if (Q_stricmp (cmd, "votell") == 0) {
   1643 		Cmd_VoiceTell_f ( ent, qtrue );
   1644 		return;
   1645 	}
   1646 	if (Q_stricmp (cmd, "vtaunt") == 0) {
   1647 		Cmd_VoiceTaunt_f ( ent );
   1648 		return;
   1649 	}
   1650 	if (Q_stricmp (cmd, "score") == 0) {
   1651 		Cmd_Score_f (ent);
   1652 		return;
   1653 	}
   1654 
   1655 	// ignore all other commands when at intermission
   1656 	if (level.intermissiontime) {
   1657 		Cmd_Say_f (ent, qfalse, qtrue);
   1658 		return;
   1659 	}
   1660 
   1661 	if (Q_stricmp (cmd, "give") == 0)
   1662 		Cmd_Give_f (ent);
   1663 	else if (Q_stricmp (cmd, "god") == 0)
   1664 		Cmd_God_f (ent);
   1665 	else if (Q_stricmp (cmd, "notarget") == 0)
   1666 		Cmd_Notarget_f (ent);
   1667 	else if (Q_stricmp (cmd, "noclip") == 0)
   1668 		Cmd_Noclip_f (ent);
   1669 	else if (Q_stricmp (cmd, "kill") == 0)
   1670 		Cmd_Kill_f (ent);
   1671 	else if (Q_stricmp (cmd, "teamtask") == 0)
   1672 		Cmd_TeamTask_f (ent);
   1673 	else if (Q_stricmp (cmd, "levelshot") == 0)
   1674 		Cmd_LevelShot_f (ent);
   1675 	else if (Q_stricmp (cmd, "follow") == 0)
   1676 		Cmd_Follow_f (ent);
   1677 	else if (Q_stricmp (cmd, "follownext") == 0)
   1678 		Cmd_FollowCycle_f (ent, 1);
   1679 	else if (Q_stricmp (cmd, "followprev") == 0)
   1680 		Cmd_FollowCycle_f (ent, -1);
   1681 	else if (Q_stricmp (cmd, "team") == 0)
   1682 		Cmd_Team_f (ent);
   1683 	else if (Q_stricmp (cmd, "where") == 0)
   1684 		Cmd_Where_f (ent);
   1685 	else if (Q_stricmp (cmd, "callvote") == 0)
   1686 		Cmd_CallVote_f (ent);
   1687 	else if (Q_stricmp (cmd, "vote") == 0)
   1688 		Cmd_Vote_f (ent);
   1689 	else if (Q_stricmp (cmd, "callteamvote") == 0)
   1690 		Cmd_CallTeamVote_f (ent);
   1691 	else if (Q_stricmp (cmd, "teamvote") == 0)
   1692 		Cmd_TeamVote_f (ent);
   1693 	else if (Q_stricmp (cmd, "gc") == 0)
   1694 		Cmd_GameCommand_f( ent );
   1695 	else if (Q_stricmp (cmd, "setviewpos") == 0)
   1696 		Cmd_SetViewpos_f( ent );
   1697 	else if (Q_stricmp (cmd, "stats") == 0)
   1698 		Cmd_Stats_f( ent );
   1699 	else
   1700 		trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) );
   1701 }