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 Quake III Arena source code; 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 */
DeathmatchScoreboardMessage(gentity_t * ent)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 */
Cmd_Score_f(gentity_t * ent)98 void Cmd_Score_f( gentity_t *ent ) {
99 	DeathmatchScoreboardMessage( ent );
100 }
101 
102 
103 
104 /*
105 ==================
106 CheatsOk
107 ==================
108 */
CheatsOk(gentity_t * ent)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 */
ConcatArgs(int start)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 */
SanitizeString(char * in,char * out)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 */
ClientNumberFromString(gentity_t * to,char * s)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 */
Cmd_Give_f(gentity_t * ent)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 */
Cmd_God_f(gentity_t * ent)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 */
Cmd_Notarget_f(gentity_t * ent)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 */
Cmd_Noclip_f(gentity_t * ent)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 */
Cmd_LevelShot_f(gentity_t * ent)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 */
Cmd_TeamTask_f(gentity_t * ent)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 */
Cmd_Kill_f(gentity_t * ent)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 */
BroadcastTeamChange(gclient_t * client,int oldTeam)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 */
SetTeam(gentity_t * ent,char * s)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 */
StopFollowing(gentity_t * ent)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 */
Cmd_Team_f(gentity_t * ent)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 */
Cmd_Follow_f(gentity_t * ent)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 */
Cmd_FollowCycle_f(gentity_t * ent,int dir)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 
G_SayTo(gentity_t * ent,gentity_t * other,int mode,int color,const char * name,const char * message)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 
G_Say(gentity_t * ent,gentity_t * target,int mode,const char * chatText)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 */
Cmd_Say_f(gentity_t * ent,int mode,qboolean arg0)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 */
Cmd_Tell_f(gentity_t * ent)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 
G_VoiceTo(gentity_t * ent,gentity_t * other,int mode,const char * id,qboolean voiceonly)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 
G_Voice(gentity_t * ent,gentity_t * target,int mode,const char * id,qboolean voiceonly)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 */
Cmd_Voice_f(gentity_t * ent,int mode,qboolean arg0,qboolean voiceonly)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 */
Cmd_VoiceTell_f(gentity_t * ent,qboolean voiceonly)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 */
Cmd_VoiceTaunt_f(gentity_t * ent)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 
Cmd_GameCommand_f(gentity_t * ent)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 */
Cmd_Where_f(gentity_t * ent)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 */
Cmd_CallVote_f(gentity_t * ent)1215 void Cmd_CallVote_f( gentity_t *ent ) {
1216 	char*	c;
1217 	int		i;
1218 	char	arg1[MAX_STRING_TOKENS];
1219 	char	arg2[MAX_STRING_TOKENS];
1220 
1221 	if ( !g_allowVote.integer ) {
1222 		trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" );
1223 		return;
1224 	}
1225 
1226 	if ( level.voteTime ) {
1227 		trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" );
1228 		return;
1229 	}
1230 	if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) {
1231 		trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" );
1232 		return;
1233 	}
1234 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
1235 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" );
1236 		return;
1237 	}
1238 
1239 	// make sure it is a valid command to vote on
1240 	trap_Argv( 1, arg1, sizeof( arg1 ) );
1241 	trap_Argv( 2, arg2, sizeof( arg2 ) );
1242 
1243 	// check for command separators in arg2
1244 	for( c = arg2; *c; ++c) {
1245 		switch(*c) {
1246 			case '\n':
1247 			case '\r':
1248 			case ';':
1249 				trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
1250 				return;
1251 			break;
1252 		}
1253 	}
1254 
1255 	if ( !Q_stricmp( arg1, "map_restart" ) ) {
1256 	} else if ( !Q_stricmp( arg1, "nextmap" ) ) {
1257 	} else if ( !Q_stricmp( arg1, "map" ) ) {
1258 	} else if ( !Q_stricmp( arg1, "g_gametype" ) ) {
1259 	} else if ( !Q_stricmp( arg1, "kick" ) ) {
1260 	} else if ( !Q_stricmp( arg1, "clientkick" ) ) {
1261 	} else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) {
1262 	} else if ( !Q_stricmp( arg1, "timelimit" ) ) {
1263 	} else if ( !Q_stricmp( arg1, "fraglimit" ) ) {
1264 	} else {
1265 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
1266 		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\"" );
1267 		return;
1268 	}
1269 
1270 	// if there is still a vote to be executed
1271 	if ( level.voteExecuteTime ) {
1272 		level.voteExecuteTime = 0;
1273 		trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) );
1274 	}
1275 
1276 	// special case for g_gametype, check for bad values
1277 	if ( !Q_stricmp( arg1, "g_gametype" ) ) {
1278 		i = atoi( arg2 );
1279 		if( i == GT_SINGLE_PLAYER || i < GT_FFA || i >= GT_MAX_GAME_TYPE) {
1280 			trap_SendServerCommand( ent-g_entities, "print \"Invalid gametype.\n\"" );
1281 			return;
1282 		}
1283 
1284 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %d", arg1, i );
1285 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, gameNames[i] );
1286 	} else if ( !Q_stricmp( arg1, "map" ) ) {
1287 		// special case for map changes, we want to reset the nextmap setting
1288 		// this allows a player to change maps, but not upset the map rotation
1289 		char	s[MAX_STRING_CHARS];
1290 
1291 		trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof(s) );
1292 		if (*s) {
1293 			Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s; set nextmap \"%s\"", arg1, arg2, s );
1294 		} else {
1295 			Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 );
1296 		}
1297 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
1298 	} else if ( !Q_stricmp( arg1, "nextmap" ) ) {
1299 		char	s[MAX_STRING_CHARS];
1300 
1301 		trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof(s) );
1302 		if (!*s) {
1303 			trap_SendServerCommand( ent-g_entities, "print \"nextmap not set.\n\"" );
1304 			return;
1305 		}
1306 		Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap");
1307 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
1308 	} else {
1309 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 );
1310 		Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
1311 	}
1312 
1313 	trap_SendServerCommand( -1, va("print \"%s called a vote.\n\"", ent->client->pers.netname ) );
1314 
1315 	// start the voting, the caller autoamtically votes yes
1316 	level.voteTime = level.time;
1317 	level.voteYes = 1;
1318 	level.voteNo = 0;
1319 
1320 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1321 		level.clients[i].ps.eFlags &= ~EF_VOTED;
1322 	}
1323 	ent->client->ps.eFlags |= EF_VOTED;
1324 
1325 	trap_SetConfigstring( CS_VOTE_TIME, va("%i", level.voteTime ) );
1326 	trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString );
1327 	trap_SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) );
1328 	trap_SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) );
1329 }
1330 
1331 /*
1332 ==================
1333 Cmd_Vote_f
1334 ==================
1335 */
Cmd_Vote_f(gentity_t * ent)1336 void Cmd_Vote_f( gentity_t *ent ) {
1337 	char		msg[64];
1338 
1339 	if ( !level.voteTime ) {
1340 		trap_SendServerCommand( ent-g_entities, "print \"No vote in progress.\n\"" );
1341 		return;
1342 	}
1343 	if ( ent->client->ps.eFlags & EF_VOTED ) {
1344 		trap_SendServerCommand( ent-g_entities, "print \"Vote already cast.\n\"" );
1345 		return;
1346 	}
1347 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
1348 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to vote as spectator.\n\"" );
1349 		return;
1350 	}
1351 
1352 	trap_SendServerCommand( ent-g_entities, "print \"Vote cast.\n\"" );
1353 
1354 	ent->client->ps.eFlags |= EF_VOTED;
1355 
1356 	trap_Argv( 1, msg, sizeof( msg ) );
1357 
1358 	if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) {
1359 		level.voteYes++;
1360 		trap_SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) );
1361 	} else {
1362 		level.voteNo++;
1363 		trap_SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) );
1364 	}
1365 
1366 	// a majority will be determined in CheckVote, which will also account
1367 	// for players entering or leaving
1368 }
1369 
1370 /*
1371 ==================
1372 Cmd_CallTeamVote_f
1373 ==================
1374 */
Cmd_CallTeamVote_f(gentity_t * ent)1375 void Cmd_CallTeamVote_f( gentity_t *ent ) {
1376 	int		i, team, cs_offset;
1377 	char	arg1[MAX_STRING_TOKENS];
1378 	char	arg2[MAX_STRING_TOKENS];
1379 
1380 	team = ent->client->sess.sessionTeam;
1381 	if ( team == TEAM_RED )
1382 		cs_offset = 0;
1383 	else if ( team == TEAM_BLUE )
1384 		cs_offset = 1;
1385 	else
1386 		return;
1387 
1388 	if ( !g_allowVote.integer ) {
1389 		trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" );
1390 		return;
1391 	}
1392 
1393 	if ( level.teamVoteTime[cs_offset] ) {
1394 		trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress.\n\"" );
1395 		return;
1396 	}
1397 	if ( ent->client->pers.teamVoteCount >= MAX_VOTE_COUNT ) {
1398 		trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of team votes.\n\"" );
1399 		return;
1400 	}
1401 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
1402 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" );
1403 		return;
1404 	}
1405 
1406 	// make sure it is a valid command to vote on
1407 	trap_Argv( 1, arg1, sizeof( arg1 ) );
1408 	arg2[0] = '\0';
1409 	for ( i = 2; i < trap_Argc(); i++ ) {
1410 		if (i > 2)
1411 			strcat(arg2, " ");
1412 		trap_Argv( i, &arg2[strlen(arg2)], sizeof( arg2 ) - strlen(arg2) );
1413 	}
1414 
1415 	if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) {
1416 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
1417 		return;
1418 	}
1419 
1420 	if ( !Q_stricmp( arg1, "leader" ) ) {
1421 		char netname[MAX_NETNAME], leader[MAX_NETNAME];
1422 
1423 		if ( !arg2[0] ) {
1424 			i = ent->client->ps.clientNum;
1425 		}
1426 		else {
1427 			// numeric values are just slot numbers
1428 			for (i = 0; i < 3; i++) {
1429 				if ( !arg2[i] || arg2[i] < '0' || arg2[i] > '9' )
1430 					break;
1431 			}
1432 			if ( i >= 3 || !arg2[i]) {
1433 				i = atoi( arg2 );
1434 				if ( i < 0 || i >= level.maxclients ) {
1435 					trap_SendServerCommand( ent-g_entities, va("print \"Bad client slot: %i\n\"", i) );
1436 					return;
1437 				}
1438 
1439 				if ( !g_entities[i].inuse ) {
1440 					trap_SendServerCommand( ent-g_entities, va("print \"Client %i is not active\n\"", i) );
1441 					return;
1442 				}
1443 			}
1444 			else {
1445 				Q_strncpyz(leader, arg2, sizeof(leader));
1446 				Q_CleanStr(leader);
1447 				for ( i = 0 ; i < level.maxclients ; i++ ) {
1448 					if ( level.clients[i].pers.connected == CON_DISCONNECTED )
1449 						continue;
1450 					if (level.clients[i].sess.sessionTeam != team)
1451 						continue;
1452 					Q_strncpyz(netname, level.clients[i].pers.netname, sizeof(netname));
1453 					Q_CleanStr(netname);
1454 					if ( !Q_stricmp(netname, leader) ) {
1455 						break;
1456 					}
1457 				}
1458 				if ( i >= level.maxclients ) {
1459 					trap_SendServerCommand( ent-g_entities, va("print \"%s is not a valid player on your team.\n\"", arg2) );
1460 					return;
1461 				}
1462 			}
1463 		}
1464 		Com_sprintf(arg2, sizeof(arg2), "%d", i);
1465 	} else {
1466 		trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
1467 		trap_SendServerCommand( ent-g_entities, "print \"Team vote commands are: leader <player>.\n\"" );
1468 		return;
1469 	}
1470 
1471 	Com_sprintf( level.teamVoteString[cs_offset], sizeof( level.teamVoteString[cs_offset] ), "%s %s", arg1, arg2 );
1472 
1473 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1474 		if ( level.clients[i].pers.connected == CON_DISCONNECTED )
1475 			continue;
1476 		if (level.clients[i].sess.sessionTeam == team)
1477 			trap_SendServerCommand( i, va("print \"%s called a team vote.\n\"", ent->client->pers.netname ) );
1478 	}
1479 
1480 	// start the voting, the caller autoamtically votes yes
1481 	level.teamVoteTime[cs_offset] = level.time;
1482 	level.teamVoteYes[cs_offset] = 1;
1483 	level.teamVoteNo[cs_offset] = 0;
1484 
1485 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1486 		if (level.clients[i].sess.sessionTeam == team)
1487 			level.clients[i].ps.eFlags &= ~EF_TEAMVOTED;
1488 	}
1489 	ent->client->ps.eFlags |= EF_TEAMVOTED;
1490 
1491 	trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va("%i", level.teamVoteTime[cs_offset] ) );
1492 	trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteString[cs_offset] );
1493 	trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) );
1494 	trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) );
1495 }
1496 
1497 /*
1498 ==================
1499 Cmd_TeamVote_f
1500 ==================
1501 */
Cmd_TeamVote_f(gentity_t * ent)1502 void Cmd_TeamVote_f( gentity_t *ent ) {
1503 	int			team, cs_offset;
1504 	char		msg[64];
1505 
1506 	team = ent->client->sess.sessionTeam;
1507 	if ( team == TEAM_RED )
1508 		cs_offset = 0;
1509 	else if ( team == TEAM_BLUE )
1510 		cs_offset = 1;
1511 	else
1512 		return;
1513 
1514 	if ( !level.teamVoteTime[cs_offset] ) {
1515 		trap_SendServerCommand( ent-g_entities, "print \"No team vote in progress.\n\"" );
1516 		return;
1517 	}
1518 	if ( ent->client->ps.eFlags & EF_TEAMVOTED ) {
1519 		trap_SendServerCommand( ent-g_entities, "print \"Team vote already cast.\n\"" );
1520 		return;
1521 	}
1522 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
1523 		trap_SendServerCommand( ent-g_entities, "print \"Not allowed to vote as spectator.\n\"" );
1524 		return;
1525 	}
1526 
1527 	trap_SendServerCommand( ent-g_entities, "print \"Team vote cast.\n\"" );
1528 
1529 	ent->client->ps.eFlags |= EF_TEAMVOTED;
1530 
1531 	trap_Argv( 1, msg, sizeof( msg ) );
1532 
1533 	if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) {
1534 		level.teamVoteYes[cs_offset]++;
1535 		trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) );
1536 	} else {
1537 		level.teamVoteNo[cs_offset]++;
1538 		trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) );
1539 	}
1540 
1541 	// a majority will be determined in TeamCheckVote, which will also account
1542 	// for players entering or leaving
1543 }
1544 
1545 
1546 /*
1547 =================
1548 Cmd_SetViewpos_f
1549 =================
1550 */
Cmd_SetViewpos_f(gentity_t * ent)1551 void Cmd_SetViewpos_f( gentity_t *ent ) {
1552 	vec3_t		origin, angles;
1553 	char		buffer[MAX_TOKEN_CHARS];
1554 	int			i;
1555 
1556 	if ( !g_cheats.integer ) {
1557 		trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\""));
1558 		return;
1559 	}
1560 	if ( trap_Argc() != 5 ) {
1561 		trap_SendServerCommand( ent-g_entities, va("print \"usage: setviewpos x y z yaw\n\""));
1562 		return;
1563 	}
1564 
1565 	VectorClear( angles );
1566 	for ( i = 0 ; i < 3 ; i++ ) {
1567 		trap_Argv( i + 1, buffer, sizeof( buffer ) );
1568 		origin[i] = atof( buffer );
1569 	}
1570 
1571 	trap_Argv( 4, buffer, sizeof( buffer ) );
1572 	angles[YAW] = atof( buffer );
1573 
1574 	TeleportPlayer( ent, origin, angles );
1575 }
1576 
1577 
1578 
1579 /*
1580 =================
1581 Cmd_Stats_f
1582 =================
1583 */
Cmd_Stats_f(gentity_t * ent)1584 void Cmd_Stats_f( gentity_t *ent ) {
1585 /*
1586 	int max, n, i;
1587 
1588 	max = trap_AAS_PointReachabilityAreaIndex( NULL );
1589 
1590 	n = 0;
1591 	for ( i = 0; i < max; i++ ) {
1592 		if ( ent->client->areabits[i >> 3] & (1 << (i & 7)) )
1593 			n++;
1594 	}
1595 
1596 	//trap_SendServerCommand( ent-g_entities, va("print \"visited %d of %d areas\n\"", n, max));
1597 	trap_SendServerCommand( ent-g_entities, va("print \"%d%% level coverage\n\"", n * 100 / max));
1598 */
1599 }
1600 
1601 /*
1602 =================
1603 ClientCommand
1604 =================
1605 */
ClientCommand(int clientNum)1606 void ClientCommand( int clientNum ) {
1607 	gentity_t *ent;
1608 	char	cmd[MAX_TOKEN_CHARS];
1609 
1610 	ent = g_entities + clientNum;
1611 	if ( !ent->client ) {
1612 		return;		// not fully in game yet
1613 	}
1614 
1615 
1616 	trap_Argv( 0, cmd, sizeof( cmd ) );
1617 
1618 	if (Q_stricmp (cmd, "say") == 0) {
1619 		Cmd_Say_f (ent, SAY_ALL, qfalse);
1620 		return;
1621 	}
1622 	if (Q_stricmp (cmd, "say_team") == 0) {
1623 		Cmd_Say_f (ent, SAY_TEAM, qfalse);
1624 		return;
1625 	}
1626 	if (Q_stricmp (cmd, "tell") == 0) {
1627 		Cmd_Tell_f ( ent );
1628 		return;
1629 	}
1630 	if (Q_stricmp (cmd, "vsay") == 0) {
1631 		Cmd_Voice_f (ent, SAY_ALL, qfalse, qfalse);
1632 		return;
1633 	}
1634 	if (Q_stricmp (cmd, "vsay_team") == 0) {
1635 		Cmd_Voice_f (ent, SAY_TEAM, qfalse, qfalse);
1636 		return;
1637 	}
1638 	if (Q_stricmp (cmd, "vtell") == 0) {
1639 		Cmd_VoiceTell_f ( ent, qfalse );
1640 		return;
1641 	}
1642 	if (Q_stricmp (cmd, "vosay") == 0) {
1643 		Cmd_Voice_f (ent, SAY_ALL, qfalse, qtrue);
1644 		return;
1645 	}
1646 	if (Q_stricmp (cmd, "vosay_team") == 0) {
1647 		Cmd_Voice_f (ent, SAY_TEAM, qfalse, qtrue);
1648 		return;
1649 	}
1650 	if (Q_stricmp (cmd, "votell") == 0) {
1651 		Cmd_VoiceTell_f ( ent, qtrue );
1652 		return;
1653 	}
1654 	if (Q_stricmp (cmd, "vtaunt") == 0) {
1655 		Cmd_VoiceTaunt_f ( ent );
1656 		return;
1657 	}
1658 	if (Q_stricmp (cmd, "score") == 0) {
1659 		Cmd_Score_f (ent);
1660 		return;
1661 	}
1662 
1663 	// ignore all other commands when at intermission
1664 	if (level.intermissiontime) {
1665 		Cmd_Say_f (ent, qfalse, qtrue);
1666 		return;
1667 	}
1668 
1669 	if (Q_stricmp (cmd, "give") == 0)
1670 		Cmd_Give_f (ent);
1671 	else if (Q_stricmp (cmd, "god") == 0)
1672 		Cmd_God_f (ent);
1673 	else if (Q_stricmp (cmd, "notarget") == 0)
1674 		Cmd_Notarget_f (ent);
1675 	else if (Q_stricmp (cmd, "noclip") == 0)
1676 		Cmd_Noclip_f (ent);
1677 	else if (Q_stricmp (cmd, "kill") == 0)
1678 		Cmd_Kill_f (ent);
1679 	else if (Q_stricmp (cmd, "teamtask") == 0)
1680 		Cmd_TeamTask_f (ent);
1681 	else if (Q_stricmp (cmd, "levelshot") == 0)
1682 		Cmd_LevelShot_f (ent);
1683 	else if (Q_stricmp (cmd, "follow") == 0)
1684 		Cmd_Follow_f (ent);
1685 	else if (Q_stricmp (cmd, "follownext") == 0)
1686 		Cmd_FollowCycle_f (ent, 1);
1687 	else if (Q_stricmp (cmd, "followprev") == 0)
1688 		Cmd_FollowCycle_f (ent, -1);
1689 	else if (Q_stricmp (cmd, "team") == 0)
1690 		Cmd_Team_f (ent);
1691 	else if (Q_stricmp (cmd, "where") == 0)
1692 		Cmd_Where_f (ent);
1693 	else if (Q_stricmp (cmd, "callvote") == 0)
1694 		Cmd_CallVote_f (ent);
1695 	else if (Q_stricmp (cmd, "vote") == 0)
1696 		Cmd_Vote_f (ent);
1697 	else if (Q_stricmp (cmd, "callteamvote") == 0)
1698 		Cmd_CallTeamVote_f (ent);
1699 	else if (Q_stricmp (cmd, "teamvote") == 0)
1700 		Cmd_TeamVote_f (ent);
1701 	else if (Q_stricmp (cmd, "gc") == 0)
1702 		Cmd_GameCommand_f( ent );
1703 	else if (Q_stricmp (cmd, "setviewpos") == 0)
1704 		Cmd_SetViewpos_f( ent );
1705 	else if (Q_stricmp (cmd, "stats") == 0)
1706 		Cmd_Stats_f( ent );
1707 	else
1708 		trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) );
1709 }
1710