1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2005 - 2015, ioquake3 contributors
7 Copyright (C) 2013 - 2015, OpenJK contributors
8 
9 This file is part of the OpenJK source code.
10 
11 OpenJK is free software; you can redistribute it and/or modify it
12 under the terms of the GNU General Public License version 2 as
13 published by the Free Software Foundation.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, see <http://www.gnu.org/licenses/>.
22 ===========================================================================
23 */
24 
25 #include "g_local.h"
26 #include "bg_saga.h"
27 
28 #include "ui/menudef.h"			// for the voice chats
29 
30 //rww - for getting bot commands...
31 int AcceptBotCommand(char *cmd, gentity_t *pl);
32 //end rww
33 
34 void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName );
35 
36 void Cmd_NPC_f( gentity_t *ent );
37 void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin);
38 
39 /*
40 ==================
41 DeathmatchScoreboardMessage
42 
43 ==================
44 */
DeathmatchScoreboardMessage(gentity_t * ent)45 void DeathmatchScoreboardMessage( gentity_t *ent ) {
46 	char		entry[1024];
47 	char		string[1400];
48 	int			stringlength;
49 	int			i, j;
50 	gclient_t	*cl;
51 	int			numSorted, scoreFlags, accuracy, perfect;
52 
53 	// send the latest information on all clients
54 	string[0] = 0;
55 	stringlength = 0;
56 	scoreFlags = 0;
57 
58 	numSorted = level.numConnectedClients;
59 
60 	if (numSorted > MAX_CLIENT_SCORE_SEND)
61 	{
62 		numSorted = MAX_CLIENT_SCORE_SEND;
63 	}
64 
65 	for (i=0 ; i < numSorted ; i++) {
66 		int		ping;
67 
68 		cl = &level.clients[level.sortedClients[i]];
69 
70 		if ( cl->pers.connected == CON_CONNECTING ) {
71 			ping = -1;
72 		} else {
73 			ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
74 		}
75 
76 		if( cl->accuracy_shots ) {
77 			accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots;
78 		}
79 		else {
80 			accuracy = 0;
81 		}
82 		perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0;
83 
84 		Com_sprintf (entry, sizeof(entry),
85 			" %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i],
86 			cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000,
87 			scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy,
88 			cl->ps.persistant[PERS_IMPRESSIVE_COUNT],
89 			cl->ps.persistant[PERS_EXCELLENT_COUNT],
90 			cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT],
91 			cl->ps.persistant[PERS_DEFEND_COUNT],
92 			cl->ps.persistant[PERS_ASSIST_COUNT],
93 			perfect,
94 			cl->ps.persistant[PERS_CAPTURES]);
95 		j = strlen(entry);
96 		if (stringlength + j > 1022)
97 			break;
98 		strcpy (string + stringlength, entry);
99 		stringlength += j;
100 	}
101 
102 	//still want to know the total # of clients
103 	i = level.numConnectedClients;
104 
105 	trap->SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i,
106 		level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE],
107 		string ) );
108 }
109 
110 
111 /*
112 ==================
113 Cmd_Score_f
114 
115 Request current scoreboard information
116 ==================
117 */
Cmd_Score_f(gentity_t * ent)118 void Cmd_Score_f( gentity_t *ent ) {
119 	DeathmatchScoreboardMessage( ent );
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 StringIsInteger
157 ==================
158 */
StringIsInteger(const char * s)159 qboolean StringIsInteger( const char *s ) {
160 	int			i=0, len=0;
161 	qboolean	foundDigit=qfalse;
162 
163 	for ( i=0, len=strlen( s ); i<len; i++ )
164 	{
165 		if ( !isdigit( s[i] ) )
166 			return qfalse;
167 
168 		foundDigit = qtrue;
169 	}
170 
171 	return foundDigit;
172 }
173 
174 /*
175 ==================
176 ClientNumberFromString
177 
178 Returns a player number for either a number or name string
179 Returns -1 if invalid
180 ==================
181 */
ClientNumberFromString(gentity_t * to,const char * s,qboolean allowconnecting)182 int ClientNumberFromString( gentity_t *to, const char *s, qboolean allowconnecting ) {
183 	gclient_t	*cl;
184 	int			idnum;
185 	char		cleanInput[MAX_NETNAME];
186 
187 	if ( StringIsInteger( s ) )
188 	{// numeric values could be slot numbers
189 		idnum = atoi( s );
190 		if ( idnum >= 0 && idnum < level.maxclients )
191 		{
192 			cl = &level.clients[idnum];
193 			if ( cl->pers.connected == CON_CONNECTED )
194 				return idnum;
195 			else if ( allowconnecting && cl->pers.connected == CON_CONNECTING )
196 				return idnum;
197 		}
198 	}
199 
200 	Q_strncpyz( cleanInput, s, sizeof(cleanInput) );
201 	Q_StripColor( cleanInput );
202 
203 	for ( idnum=0,cl=level.clients; idnum < level.maxclients; idnum++,cl++ )
204 	{// check for a name match
205 		if ( cl->pers.connected != CON_CONNECTED )
206 			if ( !allowconnecting || cl->pers.connected < CON_CONNECTING )
207 				continue;
208 
209 		if ( !Q_stricmp( cl->pers.netname_nocolor, cleanInput ) )
210 			return idnum;
211 	}
212 
213 	trap->SendServerCommand( to-g_entities, va( "print \"User %s is not on the server\n\"", s ) );
214 	return -1;
215 }
216 
217 /*
218 ==================
219 Cmd_Give_f
220 
221 Give items to a client
222 ==================
223 */
G_Give(gentity_t * ent,const char * name,const char * args,int argc)224 void G_Give( gentity_t *ent, const char *name, const char *args, int argc )
225 {
226 	gitem_t		*it;
227 	int			i;
228 	qboolean	give_all = qfalse;
229 	gentity_t	*it_ent;
230 	trace_t		trace;
231 
232 	if ( !Q_stricmp( name, "all" ) )
233 		give_all = qtrue;
234 
235 	if ( give_all )
236 	{
237 		for ( i=0; i<HI_NUM_HOLDABLE; i++ )
238 			ent->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << i);
239 	}
240 
241 	if ( give_all || !Q_stricmp( name, "health") )
242 	{
243 		if ( argc == 3 )
244 			ent->health = Com_Clampi( 1, ent->client->ps.stats[STAT_MAX_HEALTH], atoi( args ) );
245 		else
246 		{
247 			if ( level.gametype == GT_SIEGE && ent->client->siegeClass != -1 )
248 				ent->health = bgSiegeClasses[ent->client->siegeClass].maxhealth;
249 			else
250 				ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
251 		}
252 		if ( !give_all )
253 			return;
254 	}
255 
256 	if ( give_all || !Q_stricmp( name, "armor" ) || !Q_stricmp( name, "shield" ) )
257 	{
258 		if ( argc == 3 )
259 			ent->client->ps.stats[STAT_ARMOR] = Com_Clampi( 0, ent->client->ps.stats[STAT_MAX_HEALTH], atoi( args ) );
260 		else
261 		{
262 			if ( level.gametype == GT_SIEGE && ent->client->siegeClass != -1 )
263 				ent->client->ps.stats[STAT_ARMOR] = bgSiegeClasses[ent->client->siegeClass].maxarmor;
264 			else
265 				ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
266 		}
267 
268 		if ( !give_all )
269 			return;
270 	}
271 
272 	if ( give_all || !Q_stricmp( name, "force" ) )
273 	{
274 		if ( argc == 3 )
275 			ent->client->ps.fd.forcePower = Com_Clampi( 0, ent->client->ps.fd.forcePowerMax, atoi( args ) );
276 		else
277 			ent->client->ps.fd.forcePower = ent->client->ps.fd.forcePowerMax;
278 
279 		if ( !give_all )
280 			return;
281 	}
282 
283 	if ( give_all || !Q_stricmp( name, "weapons" ) )
284 	{
285 		ent->client->ps.stats[STAT_WEAPONS] = (1 << (LAST_USEABLE_WEAPON+1)) - ( 1 << WP_NONE );
286 		if ( !give_all )
287 			return;
288 	}
289 
290 	if ( !give_all && !Q_stricmp( name, "weaponnum" ) )
291 	{
292 		ent->client->ps.stats[STAT_WEAPONS] |= (1 << atoi( args ));
293 		return;
294 	}
295 
296 	if ( give_all || !Q_stricmp( name, "ammo" ) )
297 	{
298 		int num = 999;
299 		if ( argc == 3 )
300 			num = Com_Clampi( 0, 999, atoi( args ) );
301 		for ( i=AMMO_BLASTER; i<AMMO_MAX; i++ )
302 			ent->client->ps.ammo[i] = num;
303 		if ( !give_all )
304 			return;
305 	}
306 
307 	if ( !Q_stricmp( name, "excellent" ) ) {
308 		ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
309 		return;
310 	}
311 	if ( !Q_stricmp( name, "impressive" ) ) {
312 		ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
313 		return;
314 	}
315 	if ( !Q_stricmp( name, "gauntletaward" ) ) {
316 		ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
317 		return;
318 	}
319 	if ( !Q_stricmp( name, "defend" ) ) {
320 		ent->client->ps.persistant[PERS_DEFEND_COUNT]++;
321 		return;
322 	}
323 	if ( !Q_stricmp( name, "assist" ) ) {
324 		ent->client->ps.persistant[PERS_ASSIST_COUNT]++;
325 		return;
326 	}
327 
328 	// spawn a specific item right on the player
329 	if ( !give_all ) {
330 		it = BG_FindItem( name );
331 		if ( !it )
332 			return;
333 
334 		it_ent = G_Spawn();
335 		VectorCopy( ent->r.currentOrigin, it_ent->s.origin );
336 		it_ent->classname = it->classname;
337 		G_SpawnItem( it_ent, it );
338 		if ( !it_ent || !it_ent->inuse )
339 			return;
340 		FinishSpawningItem( it_ent );
341 		if ( !it_ent || !it_ent->inuse )
342 			return;
343 		memset( &trace, 0, sizeof( trace ) );
344 		Touch_Item( it_ent, ent, &trace );
345 		if ( it_ent->inuse )
346 			G_FreeEntity( it_ent );
347 	}
348 }
349 
Cmd_Give_f(gentity_t * ent)350 void Cmd_Give_f( gentity_t *ent )
351 {
352 	char name[MAX_TOKEN_CHARS] = {0};
353 
354 	trap->Argv( 1, name, sizeof( name ) );
355 	G_Give( ent, name, ConcatArgs( 2 ), trap->Argc() );
356 }
357 
Cmd_GiveOther_f(gentity_t * ent)358 void Cmd_GiveOther_f( gentity_t *ent )
359 {
360 	char		name[MAX_TOKEN_CHARS] = {0};
361 	int			i;
362 	char		otherindex[MAX_TOKEN_CHARS];
363 	gentity_t	*otherEnt = NULL;
364 
365 	if ( trap->Argc () < 3 ) {
366 		trap->SendServerCommand( ent-g_entities, "print \"Usage: giveother <player id> <givestring>\n\"" );
367 		return;
368 	}
369 
370 	trap->Argv( 1, otherindex, sizeof( otherindex ) );
371 	i = ClientNumberFromString( ent, otherindex, qfalse );
372 	if ( i == -1 ) {
373 		return;
374 	}
375 
376 	otherEnt = &g_entities[i];
377 	if ( !otherEnt->inuse || !otherEnt->client ) {
378 		return;
379 	}
380 
381 	if ( (otherEnt->health <= 0 || otherEnt->client->tempSpectate >= level.time || otherEnt->client->sess.sessionTeam == TEAM_SPECTATOR) )
382 	{
383 		// Intentionally displaying for the command user
384 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) );
385 		return;
386 	}
387 
388 	trap->Argv( 2, name, sizeof( name ) );
389 
390 	G_Give( otherEnt, name, ConcatArgs( 3 ), trap->Argc()-1 );
391 }
392 
393 /*
394 ==================
395 Cmd_God_f
396 
397 Sets client to godmode
398 
399 argv(0) god
400 ==================
401 */
Cmd_God_f(gentity_t * ent)402 void Cmd_God_f( gentity_t *ent ) {
403 	char *msg = NULL;
404 
405 	ent->flags ^= FL_GODMODE;
406 	if ( !(ent->flags & FL_GODMODE) )
407 		msg = "godmode OFF";
408 	else
409 		msg = "godmode ON";
410 
411 	trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", msg ) );
412 }
413 
414 
415 /*
416 ==================
417 Cmd_Notarget_f
418 
419 Sets client to notarget
420 
421 argv(0) notarget
422 ==================
423 */
Cmd_Notarget_f(gentity_t * ent)424 void Cmd_Notarget_f( gentity_t *ent ) {
425 	char *msg = NULL;
426 
427 	ent->flags ^= FL_NOTARGET;
428 	if ( !(ent->flags & FL_NOTARGET) )
429 		msg = "notarget OFF";
430 	else
431 		msg = "notarget ON";
432 
433 	trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", msg ) );
434 }
435 
436 
437 /*
438 ==================
439 Cmd_Noclip_f
440 
441 argv(0) noclip
442 ==================
443 */
Cmd_Noclip_f(gentity_t * ent)444 void Cmd_Noclip_f( gentity_t *ent ) {
445 	char *msg = NULL;
446 
447 	ent->client->noclip = !ent->client->noclip;
448 	if ( !ent->client->noclip )
449 		msg = "noclip OFF";
450 	else
451 		msg = "noclip ON";
452 
453 	trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", msg ) );
454 }
455 
456 
457 /*
458 ==================
459 Cmd_LevelShot_f
460 
461 This is just to help generate the level pictures
462 for the menus.  It goes to the intermission immediately
463 and sends over a command to the client to resize the view,
464 hide the scoreboard, and take a special screenshot
465 ==================
466 */
Cmd_LevelShot_f(gentity_t * ent)467 void Cmd_LevelShot_f( gentity_t *ent )
468 {
469 	if ( !ent->client->pers.localClient )
470 	{
471 		trap->SendServerCommand(ent-g_entities, "print \"The levelshot command must be executed by a local client\n\"");
472 		return;
473 	}
474 
475 	// doesn't work in single player
476 	if ( level.gametype == GT_SINGLE_PLAYER )
477 	{
478 		trap->SendServerCommand(ent-g_entities, "print \"Must not be in singleplayer mode for levelshot\n\"" );
479 		return;
480 	}
481 
482 	BeginIntermission();
483 	trap->SendServerCommand( ent-g_entities, "clientLevelShot" );
484 }
485 
486 #if 0
487 /*
488 ==================
489 Cmd_TeamTask_f
490 
491 From TA.
492 ==================
493 */
494 void Cmd_TeamTask_f( gentity_t *ent ) {
495 	char userinfo[MAX_INFO_STRING];
496 	char		arg[MAX_TOKEN_CHARS];
497 	int task;
498 	int client = ent->client - level.clients;
499 
500 	if ( trap->Argc() != 2 ) {
501 		return;
502 	}
503 	trap->Argv( 1, arg, sizeof( arg ) );
504 	task = atoi( arg );
505 
506 	trap->GetUserinfo(client, userinfo, sizeof(userinfo));
507 	Info_SetValueForKey(userinfo, "teamtask", va("%d", task));
508 	trap->SetUserinfo(client, userinfo);
509 	ClientUserinfoChanged(client);
510 }
511 #endif
512 
G_Kill(gentity_t * ent)513 void G_Kill( gentity_t *ent ) {
514 	if ((level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) &&
515 		level.numPlayingClients > 1 && !level.warmupTime)
516 	{
517 		if (!g_allowDuelSuicide.integer)
518 		{
519 			trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "ATTEMPTDUELKILL")) );
520 			return;
521 		}
522 	}
523 
524 	ent->flags &= ~FL_GODMODE;
525 	ent->client->ps.stats[STAT_HEALTH] = ent->health = -999;
526 	player_die (ent, ent, ent, 100000, MOD_SUICIDE);
527 }
528 
529 /*
530 =================
531 Cmd_Kill_f
532 =================
533 */
Cmd_Kill_f(gentity_t * ent)534 void Cmd_Kill_f( gentity_t *ent ) {
535 	G_Kill( ent );
536 }
537 
Cmd_KillOther_f(gentity_t * ent)538 void Cmd_KillOther_f( gentity_t *ent )
539 {
540 	int			i;
541 	char		otherindex[MAX_TOKEN_CHARS];
542 	gentity_t	*otherEnt = NULL;
543 
544 	if ( trap->Argc () < 2 ) {
545 		trap->SendServerCommand( ent-g_entities, "print \"Usage: killother <player id>\n\"" );
546 		return;
547 	}
548 
549 	trap->Argv( 1, otherindex, sizeof( otherindex ) );
550 	i = ClientNumberFromString( ent, otherindex, qfalse );
551 	if ( i == -1 ) {
552 		return;
553 	}
554 
555 	otherEnt = &g_entities[i];
556 	if ( !otherEnt->inuse || !otherEnt->client ) {
557 		return;
558 	}
559 
560 	if ( (otherEnt->health <= 0 || otherEnt->client->tempSpectate >= level.time || otherEnt->client->sess.sessionTeam == TEAM_SPECTATOR) )
561 	{
562 		// Intentionally displaying for the command user
563 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) );
564 		return;
565 	}
566 
567 	G_Kill( otherEnt );
568 }
569 
570 /*
571 =================
572 BroadCastTeamChange
573 
574 Let everyone know about a team change
575 =================
576 */
BroadcastTeamChange(gclient_t * client,int oldTeam)577 void BroadcastTeamChange( gclient_t *client, int oldTeam )
578 {
579 	client->ps.fd.forceDoInit = 1; //every time we change teams make sure our force powers are set right
580 
581 	if (level.gametype == GT_SIEGE)
582 	{ //don't announce these things in siege
583 		return;
584 	}
585 
586 	if ( client->sess.sessionTeam == TEAM_RED ) {
587 		trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"",
588 			client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEREDTEAM")) );
589 	} else if ( client->sess.sessionTeam == TEAM_BLUE ) {
590 		trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"",
591 		client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBLUETEAM")));
592 	} else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) {
593 		trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"",
594 		client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHESPECTATORS")));
595 	} else if ( client->sess.sessionTeam == TEAM_FREE ) {
596 		trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"",
597 		client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBATTLE")));
598 	}
599 
600 	G_LogPrintf( "ChangeTeam: %i [%s] (%s) \"%s^7\" %s -> %s\n", (int)(client - level.clients), client->sess.IP, client->pers.guid, client->pers.netname, TeamName( oldTeam ), TeamName( client->sess.sessionTeam ) );
601 }
602 
G_PowerDuelCheckFail(gentity_t * ent)603 qboolean G_PowerDuelCheckFail(gentity_t *ent)
604 {
605 	int			loners = 0;
606 	int			doubles = 0;
607 
608 	if (!ent->client || ent->client->sess.duelTeam == DUELTEAM_FREE)
609 	{
610 		return qtrue;
611 	}
612 
613 	G_PowerDuelCount(&loners, &doubles, qfalse);
614 
615 	if (ent->client->sess.duelTeam == DUELTEAM_LONE && loners >= 1)
616 	{
617 		return qtrue;
618 	}
619 
620 	if (ent->client->sess.duelTeam == DUELTEAM_DOUBLE && doubles >= 2)
621 	{
622 		return qtrue;
623 	}
624 
625 	return qfalse;
626 }
627 
628 /*
629 =================
630 SetTeam
631 =================
632 */
633 qboolean g_dontPenalizeTeam = qfalse;
634 qboolean g_preventTeamBegin = qfalse;
SetTeam(gentity_t * ent,char * s)635 void SetTeam( gentity_t *ent, char *s ) {
636 	int					team, oldTeam;
637 	gclient_t			*client;
638 	int					clientNum;
639 	spectatorState_t	specState;
640 	int					specClient;
641 	int					teamLeader;
642 
643 	// fix: this prevents rare creation of invalid players
644 	if (!ent->inuse)
645 	{
646 		return;
647 	}
648 
649 	//
650 	// see what change is requested
651 	//
652 	client = ent->client;
653 
654 	clientNum = client - level.clients;
655 	specClient = 0;
656 	specState = SPECTATOR_NOT;
657 	if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" )  ) {
658 		team = TEAM_SPECTATOR;
659 		specState = SPECTATOR_FREE; // SPECTATOR_SCOREBOARD disabling this for now since it is totally broken on client side
660 	} else if ( !Q_stricmp( s, "follow1" ) ) {
661 		team = TEAM_SPECTATOR;
662 		specState = SPECTATOR_FOLLOW;
663 		specClient = -1;
664 	} else if ( !Q_stricmp( s, "follow2" ) ) {
665 		team = TEAM_SPECTATOR;
666 		specState = SPECTATOR_FOLLOW;
667 		specClient = -2;
668 	} else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) {
669 		team = TEAM_SPECTATOR;
670 		specState = SPECTATOR_FREE;
671 	} else if ( level.gametype >= GT_TEAM ) {
672 		// if running a team game, assign player to one of the teams
673 		specState = SPECTATOR_NOT;
674 		if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) {
675 			team = TEAM_RED;
676 		} else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) {
677 			team = TEAM_BLUE;
678 		} else {
679 			// pick the team with the least number of players
680 			//For now, don't do this. The legalize function will set powers properly now.
681 			/*
682 			if (g_forceBasedTeams.integer)
683 			{
684 				if (ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
685 				{
686 					team = TEAM_BLUE;
687 				}
688 				else
689 				{
690 					team = TEAM_RED;
691 				}
692 			}
693 			else
694 			{
695 			*/
696 				team = PickTeam( clientNum );
697 			//}
698 		}
699 
700 		if ( g_teamForceBalance.integer && !g_jediVmerc.integer ) {
701 			int		counts[TEAM_NUM_TEAMS];
702 
703 			//JAC: Invalid clientNum was being used
704 			counts[TEAM_BLUE] = TeamCount( ent-g_entities, TEAM_BLUE );
705 			counts[TEAM_RED] = TeamCount( ent-g_entities, TEAM_RED );
706 
707 			// We allow a spread of two
708 			if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) {
709 				//For now, don't do this. The legalize function will set powers properly now.
710 				/*
711 				if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_DARKSIDE)
712 				{
713 					trap->SendServerCommand( ent->client->ps.clientNum,
714 						va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED_SWITCH")) );
715 				}
716 				else
717 				*/
718 				{
719 					//JAC: Invalid clientNum was being used
720 					trap->SendServerCommand( ent-g_entities,
721 						va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED")) );
722 				}
723 				return; // ignore the request
724 			}
725 			if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) {
726 				//For now, don't do this. The legalize function will set powers properly now.
727 				/*
728 				if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
729 				{
730 					trap->SendServerCommand( ent->client->ps.clientNum,
731 						va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE_SWITCH")) );
732 				}
733 				else
734 				*/
735 				{
736 					//JAC: Invalid clientNum was being used
737 					trap->SendServerCommand( ent-g_entities,
738 						va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE")) );
739 				}
740 				return; // ignore the request
741 			}
742 
743 			// It's ok, the team we are switching to has less or same number of players
744 		}
745 
746 		//For now, don't do this. The legalize function will set powers properly now.
747 		/*
748 		if (g_forceBasedTeams.integer)
749 		{
750 			if (team == TEAM_BLUE && ent->client->ps.fd.forceSide != FORCE_LIGHTSIDE)
751 			{
752 				trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBELIGHT")) );
753 				return;
754 			}
755 			if (team == TEAM_RED && ent->client->ps.fd.forceSide != FORCE_DARKSIDE)
756 			{
757 				trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBEDARK")) );
758 				return;
759 			}
760 		}
761 		*/
762 
763 	} else {
764 		// force them to spectators if there aren't any spots free
765 		team = TEAM_FREE;
766 	}
767 
768 	oldTeam = client->sess.sessionTeam;
769 
770 	if (level.gametype == GT_SIEGE)
771 	{
772 		if (client->tempSpectate >= level.time &&
773 			team == TEAM_SPECTATOR)
774 		{ //sorry, can't do that.
775 			return;
776 		}
777 
778 		if ( team == oldTeam && team != TEAM_SPECTATOR )
779 			return;
780 
781 		client->sess.siegeDesiredTeam = team;
782 		//oh well, just let them go.
783 		/*
784 		if (team != TEAM_SPECTATOR)
785 		{ //can't switch to anything in siege unless you want to switch to being a fulltime spectator
786 			//fill them in on their objectives for this team now
787 			trap->SendServerCommand(ent-g_entities, va("sb %i", client->sess.siegeDesiredTeam));
788 
789 			trap->SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time the round begins.\n\"") );
790 			return;
791 		}
792 		*/
793 		if (client->sess.sessionTeam != TEAM_SPECTATOR &&
794 			team != TEAM_SPECTATOR)
795 		{ //not a spectator now, and not switching to spec, so you have to wait til you die.
796 			//trap->SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time you respawn.\n\"") );
797 			qboolean doBegin;
798 			if (ent->client->tempSpectate >= level.time)
799 			{
800 				doBegin = qfalse;
801 			}
802 			else
803 			{
804 				doBegin = qtrue;
805 			}
806 
807 			if (doBegin)
808 			{
809 				// Kill them so they automatically respawn in the team they wanted.
810 				if (ent->health > 0)
811 				{
812 					ent->flags &= ~FL_GODMODE;
813 					ent->client->ps.stats[STAT_HEALTH] = ent->health = 0;
814 					player_die( ent, ent, ent, 100000, MOD_TEAM_CHANGE );
815 				}
816 			}
817 
818 			if (ent->client->sess.sessionTeam != ent->client->sess.siegeDesiredTeam)
819 			{
820 				SetTeamQuick(ent, ent->client->sess.siegeDesiredTeam, qfalse);
821 			}
822 
823 			return;
824 		}
825 	}
826 
827 	// override decision if limiting the players
828 	if ( (level.gametype == GT_DUEL)
829 		&& level.numNonSpectatorClients >= 2 )
830 	{
831 		team = TEAM_SPECTATOR;
832 	}
833 	else if ( (level.gametype == GT_POWERDUEL)
834 		&& (level.numPlayingClients >= 3 || G_PowerDuelCheckFail(ent)) )
835 	{
836 		team = TEAM_SPECTATOR;
837 	}
838 	else if ( g_maxGameClients.integer > 0 &&
839 		level.numNonSpectatorClients >= g_maxGameClients.integer )
840 	{
841 		team = TEAM_SPECTATOR;
842 	}
843 
844 	//
845 	// decide if we will allow the change
846 	//
847 	if ( team == oldTeam && team != TEAM_SPECTATOR ) {
848 		return;
849 	}
850 
851 	//
852 	// execute the team change
853 	//
854 
855 	//If it's siege then show the mission briefing for the team you just joined.
856 //	if (level.gametype == GT_SIEGE && team != TEAM_SPECTATOR)
857 //	{
858 //		trap->SendServerCommand(clientNum, va("sb %i", team));
859 //	}
860 
861 	// if the player was dead leave the body
862 	if ( client->ps.stats[STAT_HEALTH] <= 0 && client->sess.sessionTeam != TEAM_SPECTATOR ) {
863 		MaintainBodyQueue(ent);
864 	}
865 
866 	// he starts at 'base'
867 	client->pers.teamState.state = TEAM_BEGIN;
868 	if ( oldTeam != TEAM_SPECTATOR ) {
869 		// Kill him (makes sure he loses flags, etc)
870 		ent->flags &= ~FL_GODMODE;
871 		ent->client->ps.stats[STAT_HEALTH] = ent->health = 0;
872 		g_dontPenalizeTeam = qtrue;
873 		player_die (ent, ent, ent, 100000, MOD_SUICIDE);
874 		g_dontPenalizeTeam = qfalse;
875 
876 	}
877 	// they go to the end of the line for tournaments
878 	if ( team == TEAM_SPECTATOR && oldTeam != team )
879 		AddTournamentQueue( client );
880 
881 	// clear votes if going to spectator (specs can't vote)
882 	if ( team == TEAM_SPECTATOR )
883 		G_ClearVote( ent );
884 	// also clear team votes if switching red/blue or going to spec
885 	G_ClearTeamVote( ent, oldTeam );
886 
887 	client->sess.sessionTeam = (team_t)team;
888 	client->sess.spectatorState = specState;
889 	client->sess.spectatorClient = specClient;
890 
891 	client->sess.teamLeader = qfalse;
892 	if ( team == TEAM_RED || team == TEAM_BLUE ) {
893 		teamLeader = TeamLeader( team );
894 		// if there is no team leader or the team leader is a bot and this client is not a bot
895 		if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) {
896 			//SetLeader( team, clientNum );
897 		}
898 	}
899 	// make sure there is a team leader on the team the player came from
900 	if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) {
901 		CheckTeamLeader( oldTeam );
902 	}
903 
904 	BroadcastTeamChange( client, oldTeam );
905 
906 	//make a disappearing effect where they were before teleporting them to the appropriate spawn point,
907 	//if we were not on the spec team
908 	if (oldTeam != TEAM_SPECTATOR)
909 	{
910 		gentity_t *tent = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT );
911 		tent->s.clientNum = clientNum;
912 	}
913 
914 	// get and distribute relevent paramters
915 	if ( !ClientUserinfoChanged( clientNum ) )
916 		return;
917 
918 	if (!g_preventTeamBegin)
919 	{
920 		ClientBegin( clientNum, qfalse );
921 	}
922 }
923 
924 /*
925 =================
926 StopFollowing
927 
928 If the client being followed leaves the game, or you just want to drop
929 to free floating spectator mode
930 =================
931 */
932 extern void G_LeaveVehicle( gentity_t *ent, qboolean ConCheck );
StopFollowing(gentity_t * ent)933 void StopFollowing( gentity_t *ent ) {
934 	int i=0;
935 	ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;
936 	ent->client->sess.sessionTeam = TEAM_SPECTATOR;
937 	ent->client->sess.spectatorState = SPECTATOR_FREE;
938 	ent->client->ps.pm_flags &= ~PMF_FOLLOW;
939 	ent->r.svFlags &= ~SVF_BOT;
940 	ent->client->ps.clientNum = ent - g_entities;
941 	ent->client->ps.weapon = WP_NONE;
942 	G_LeaveVehicle( ent, qfalse ); // clears m_iVehicleNum as well
943 	ent->client->ps.emplacedIndex = 0;
944 	//ent->client->ps.m_iVehicleNum = 0;
945 	ent->client->ps.viewangles[ROLL] = 0.0f;
946 	ent->client->ps.forceHandExtend = HANDEXTEND_NONE;
947 	ent->client->ps.forceHandExtendTime = 0;
948 	ent->client->ps.zoomMode = 0;
949 	ent->client->ps.zoomLocked = qfalse;
950 	ent->client->ps.zoomLockTime = 0;
951 	ent->client->ps.saberMove = LS_NONE;
952 	ent->client->ps.legsAnim = 0;
953 	ent->client->ps.legsTimer = 0;
954 	ent->client->ps.torsoAnim = 0;
955 	ent->client->ps.torsoTimer = 0;
956 	ent->client->ps.isJediMaster = qfalse; // major exploit if you are spectating somebody and they are JM and you reconnect
957 	ent->client->ps.cloakFuel = 100; // so that fuel goes away after stop following them
958 	ent->client->ps.jetpackFuel = 100; // so that fuel goes away after stop following them
959 	ent->health = ent->client->ps.stats[STAT_HEALTH] = 100; // so that you don't keep dead angles if you were spectating a dead person
960 	ent->client->ps.bobCycle = 0;
961 	ent->client->ps.pm_type = PM_SPECTATOR;
962 	ent->client->ps.eFlags &= ~EF_DISINTEGRATION;
963 	for ( i=0; i<PW_NUM_POWERUPS; i++ )
964 		ent->client->ps.powerups[i] = 0;
965 }
966 
967 /*
968 =================
969 Cmd_Team_f
970 =================
971 */
Cmd_Team_f(gentity_t * ent)972 void Cmd_Team_f( gentity_t *ent ) {
973 	int			oldTeam;
974 	char		s[MAX_TOKEN_CHARS];
975 
976 	oldTeam = ent->client->sess.sessionTeam;
977 
978 	if ( trap->Argc() != 2 ) {
979 		switch ( oldTeam ) {
980 		case TEAM_BLUE:
981 			trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTBLUETEAM")) );
982 			break;
983 		case TEAM_RED:
984 			trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTREDTEAM")) );
985 			break;
986 		case TEAM_FREE:
987 			trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTFREETEAM")) );
988 			break;
989 		case TEAM_SPECTATOR:
990 			trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTSPECTEAM")) );
991 			break;
992 		}
993 		return;
994 	}
995 
996 	if ( ent->client->switchTeamTime > level.time ) {
997 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) );
998 		return;
999 	}
1000 
1001 	if (gEscaping)
1002 	{
1003 		return;
1004 	}
1005 
1006 	// if they are playing a tournament game, count as a loss
1007 	if ( level.gametype == GT_DUEL
1008 		&& ent->client->sess.sessionTeam == TEAM_FREE ) {//in a tournament game
1009 		//disallow changing teams
1010 		trap->SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Duel\n\"" );
1011 		return;
1012 		//FIXME: why should this be a loss???
1013 		//ent->client->sess.losses++;
1014 	}
1015 
1016 	if (level.gametype == GT_POWERDUEL)
1017 	{ //don't let clients change teams manually at all in powerduel, it will be taken care of through automated stuff
1018 		trap->SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Power Duel\n\"" );
1019 		return;
1020 	}
1021 
1022 	trap->Argv( 1, s, sizeof( s ) );
1023 
1024 	SetTeam( ent, s );
1025 
1026 	// fix: update team switch time only if team change really happend
1027 	if (oldTeam != ent->client->sess.sessionTeam)
1028 		ent->client->switchTeamTime = level.time + 5000;
1029 }
1030 
1031 /*
1032 =================
1033 Cmd_DuelTeam_f
1034 =================
1035 */
Cmd_DuelTeam_f(gentity_t * ent)1036 void Cmd_DuelTeam_f(gentity_t *ent)
1037 {
1038 	int			oldTeam;
1039 	char		s[MAX_TOKEN_CHARS];
1040 
1041 	if (level.gametype != GT_POWERDUEL)
1042 	{ //don't bother doing anything if this is not power duel
1043 		return;
1044 	}
1045 
1046 	/*
1047 	if (ent->client->sess.sessionTeam != TEAM_SPECTATOR)
1048 	{
1049 		trap->SendServerCommand( ent-g_entities, va("print \"You cannot change your duel team unless you are a spectator.\n\""));
1050 		return;
1051 	}
1052 	*/
1053 
1054 	if ( trap->Argc() != 2 )
1055 	{ //No arg so tell what team we're currently on.
1056 		oldTeam = ent->client->sess.duelTeam;
1057 		switch ( oldTeam )
1058 		{
1059 		case DUELTEAM_FREE:
1060 			trap->SendServerCommand( ent-g_entities, va("print \"None\n\"") );
1061 			break;
1062 		case DUELTEAM_LONE:
1063 			trap->SendServerCommand( ent-g_entities, va("print \"Single\n\"") );
1064 			break;
1065 		case DUELTEAM_DOUBLE:
1066 			trap->SendServerCommand( ent-g_entities, va("print \"Double\n\"") );
1067 			break;
1068 		default:
1069 			break;
1070 		}
1071 		return;
1072 	}
1073 
1074 	if ( ent->client->switchDuelTeamTime > level.time )
1075 	{ //debounce for changing
1076 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) );
1077 		return;
1078 	}
1079 
1080 	trap->Argv( 1, s, sizeof( s ) );
1081 
1082 	oldTeam = ent->client->sess.duelTeam;
1083 
1084 	if (!Q_stricmp(s, "free"))
1085 	{
1086 		ent->client->sess.duelTeam = DUELTEAM_FREE;
1087 	}
1088 	else if (!Q_stricmp(s, "single"))
1089 	{
1090 		ent->client->sess.duelTeam = DUELTEAM_LONE;
1091 	}
1092 	else if (!Q_stricmp(s, "double"))
1093 	{
1094 		ent->client->sess.duelTeam = DUELTEAM_DOUBLE;
1095 	}
1096 	else
1097 	{
1098 		trap->SendServerCommand( ent-g_entities, va("print \"'%s' not a valid duel team.\n\"", s) );
1099 	}
1100 
1101 	if (oldTeam == ent->client->sess.duelTeam)
1102 	{ //didn't actually change, so don't care.
1103 		return;
1104 	}
1105 
1106 	if (ent->client->sess.sessionTeam != TEAM_SPECTATOR)
1107 	{ //ok..die
1108 		int curTeam = ent->client->sess.duelTeam;
1109 		ent->client->sess.duelTeam = oldTeam;
1110 		G_Damage(ent, ent, ent, NULL, ent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
1111 		ent->client->sess.duelTeam = curTeam;
1112 	}
1113 	//reset wins and losses
1114 	ent->client->sess.wins = 0;
1115 	ent->client->sess.losses = 0;
1116 
1117 	//get and distribute relevent paramters
1118 	if ( ClientUserinfoChanged( ent->s.number ) )
1119 		return;
1120 
1121 	ent->client->switchDuelTeamTime = level.time + 5000;
1122 }
1123 
G_TeamForSiegeClass(const char * clName)1124 int G_TeamForSiegeClass(const char *clName)
1125 {
1126 	int i = 0;
1127 	int team = SIEGETEAM_TEAM1;
1128 	siegeTeam_t *stm = BG_SiegeFindThemeForTeam(team);
1129 	siegeClass_t *scl;
1130 
1131 	if (!stm)
1132 	{
1133 		return 0;
1134 	}
1135 
1136 	while (team <= SIEGETEAM_TEAM2)
1137 	{
1138 		scl = stm->classes[i];
1139 
1140 		if (scl && scl->name[0])
1141 		{
1142 			if (!Q_stricmp(clName, scl->name))
1143 			{
1144 				return team;
1145 			}
1146 		}
1147 
1148 		i++;
1149 		if (i >= MAX_SIEGE_CLASSES || i >= stm->numClasses)
1150 		{
1151 			if (team == SIEGETEAM_TEAM2)
1152 			{
1153 				break;
1154 			}
1155 			team = SIEGETEAM_TEAM2;
1156 			stm = BG_SiegeFindThemeForTeam(team);
1157 			i = 0;
1158 		}
1159 	}
1160 
1161 	return 0;
1162 }
1163 
1164 /*
1165 =================
1166 Cmd_SiegeClass_f
1167 =================
1168 */
Cmd_SiegeClass_f(gentity_t * ent)1169 void Cmd_SiegeClass_f( gentity_t *ent )
1170 {
1171 	char className[64];
1172 	int team = 0;
1173 	int preScore;
1174 	qboolean startedAsSpec = qfalse;
1175 
1176 	if (level.gametype != GT_SIEGE)
1177 	{ //classes are only valid for this gametype
1178 		return;
1179 	}
1180 
1181 	if (!ent->client)
1182 	{
1183 		return;
1184 	}
1185 
1186 	if (trap->Argc() < 1)
1187 	{
1188 		return;
1189 	}
1190 
1191 	if ( ent->client->switchClassTime > level.time )
1192 	{
1193 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSSWITCH")) );
1194 		return;
1195 	}
1196 
1197 	if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
1198 	{
1199 		startedAsSpec = qtrue;
1200 	}
1201 
1202 	trap->Argv( 1, className, sizeof( className ) );
1203 
1204 	team = G_TeamForSiegeClass(className);
1205 
1206 	if (!team)
1207 	{ //not a valid class name
1208 		return;
1209 	}
1210 
1211 	if (ent->client->sess.sessionTeam != team)
1212 	{ //try changing it then
1213 		g_preventTeamBegin = qtrue;
1214 		if (team == TEAM_RED)
1215 		{
1216 			SetTeam(ent, "red");
1217 		}
1218 		else if (team == TEAM_BLUE)
1219 		{
1220 			SetTeam(ent, "blue");
1221 		}
1222 		g_preventTeamBegin = qfalse;
1223 
1224 		if (ent->client->sess.sessionTeam != team)
1225 		{ //failed, oh well
1226 			if (ent->client->sess.sessionTeam != TEAM_SPECTATOR ||
1227 				ent->client->sess.siegeDesiredTeam != team)
1228 			{
1229 				trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSTEAM")) );
1230 				return;
1231 			}
1232 		}
1233 	}
1234 
1235 	//preserve 'is score
1236 	preScore = ent->client->ps.persistant[PERS_SCORE];
1237 
1238 	//Make sure the class is valid for the team
1239 	BG_SiegeCheckClassLegality(team, className);
1240 
1241 	//Set the session data
1242 	strcpy(ent->client->sess.siegeClass, className);
1243 
1244 	// get and distribute relevent paramters
1245 	if ( !ClientUserinfoChanged( ent->s.number ) )
1246 		return;
1247 
1248 	if (ent->client->tempSpectate < level.time)
1249 	{
1250 		// Kill him (makes sure he loses flags, etc)
1251 		if (ent->health > 0 && !startedAsSpec)
1252 		{
1253 			ent->flags &= ~FL_GODMODE;
1254 			ent->client->ps.stats[STAT_HEALTH] = ent->health = 0;
1255 			player_die (ent, ent, ent, 100000, MOD_SUICIDE);
1256 		}
1257 
1258 		if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || startedAsSpec)
1259 		{ //respawn them instantly.
1260 			ClientBegin( ent->s.number, qfalse );
1261 		}
1262 	}
1263 	//set it back after we do all the stuff
1264 	ent->client->ps.persistant[PERS_SCORE] = preScore;
1265 
1266 	ent->client->switchClassTime = level.time + 5000;
1267 }
1268 
1269 /*
1270 =================
1271 Cmd_ForceChanged_f
1272 =================
1273 */
Cmd_ForceChanged_f(gentity_t * ent)1274 void Cmd_ForceChanged_f( gentity_t *ent )
1275 {
1276 	char fpChStr[1024];
1277 	const char *buf;
1278 //	Cmd_Kill_f(ent);
1279 	if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
1280 	{ //if it's a spec, just make the changes now
1281 		//trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "FORCEAPPLIED")) );
1282 		//No longer print it, as the UI calls this a lot.
1283 		WP_InitForcePowers( ent );
1284 		goto argCheck;
1285 	}
1286 
1287 	buf = G_GetStringEdString("MP_SVGAME", "FORCEPOWERCHANGED");
1288 
1289 	strcpy(fpChStr, buf);
1290 
1291 	trap->SendServerCommand( ent-g_entities, va("print \"%s%s\n\"", S_COLOR_GREEN, fpChStr) );
1292 
1293 	ent->client->ps.fd.forceDoInit = 1;
1294 argCheck:
1295 	if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
1296 	{ //If this is duel, don't even bother changing team in relation to this.
1297 		return;
1298 	}
1299 
1300 	if (trap->Argc() > 1)
1301 	{
1302 		char	arg[MAX_TOKEN_CHARS];
1303 
1304 		trap->Argv( 1, arg, sizeof( arg ) );
1305 
1306 		if ( arg[0] )
1307 		{ //if there's an arg, assume it's a combo team command from the UI.
1308 			Cmd_Team_f(ent);
1309 		}
1310 	}
1311 }
1312 
1313 extern qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel );
1314 extern qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel );
G_SetSaber(gentity_t * ent,int saberNum,char * saberName,qboolean siegeOverride)1315 qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride)
1316 {
1317 	char truncSaberName[MAX_QPATH] = {0};
1318 
1319 	if ( !siegeOverride && level.gametype == GT_SIEGE && ent->client->siegeClass != -1 &&
1320 		(bgSiegeClasses[ent->client->siegeClass].saberStance || bgSiegeClasses[ent->client->siegeClass].saber1[0] || bgSiegeClasses[ent->client->siegeClass].saber2[0]) )
1321 	{ //don't let it be changed if the siege class has forced any saber-related things
1322 		return qfalse;
1323 	}
1324 
1325 	Q_strncpyz( truncSaberName, saberName, sizeof( truncSaberName ) );
1326 
1327 	if ( saberNum == 0 && (!Q_stricmp( "none", truncSaberName ) || !Q_stricmp( "remove", truncSaberName )) )
1328 	{ //can't remove saber 0 like this
1329 		Q_strncpyz( truncSaberName, DEFAULT_SABER, sizeof( truncSaberName ) );
1330 	}
1331 
1332 	//Set the saber with the arg given. If the arg is
1333 	//not a valid sabername defaults will be used.
1334 	WP_SetSaber( ent->s.number, ent->client->saber, saberNum, truncSaberName );
1335 
1336 	if ( !ent->client->saber[0].model[0] )
1337 	{
1338 		assert(0); //should never happen!
1339 		Q_strncpyz( ent->client->pers.saber1, DEFAULT_SABER, sizeof( ent->client->pers.saber1 ) );
1340 	}
1341 	else
1342 		Q_strncpyz( ent->client->pers.saber1, ent->client->saber[0].name, sizeof( ent->client->pers.saber1 ) );
1343 
1344 	if ( !ent->client->saber[1].model[0] )
1345 		Q_strncpyz( ent->client->pers.saber2, "none", sizeof( ent->client->pers.saber2 ) );
1346 	else
1347 		Q_strncpyz( ent->client->pers.saber2, ent->client->saber[1].name, sizeof( ent->client->pers.saber2 ) );
1348 
1349 	if ( !WP_SaberStyleValidForSaber( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, ent->client->ps.fd.saberAnimLevel ) )
1350 	{
1351 		WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &ent->client->ps.fd.saberAnimLevel );
1352 		ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = ent->client->ps.fd.saberAnimLevel;
1353 	}
1354 
1355 	return qtrue;
1356 }
1357 
1358 /*
1359 =================
1360 Cmd_Follow_f
1361 =================
1362 */
Cmd_Follow_f(gentity_t * ent)1363 void Cmd_Follow_f( gentity_t *ent ) {
1364 	int		i;
1365 	char	arg[MAX_TOKEN_CHARS];
1366 
1367 	if ( ent->client->sess.spectatorState == SPECTATOR_NOT && ent->client->switchTeamTime > level.time ) {
1368 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) );
1369 		return;
1370 	}
1371 
1372 	if ( trap->Argc() != 2 ) {
1373 		if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
1374 			StopFollowing( ent );
1375 		}
1376 		return;
1377 	}
1378 
1379 	trap->Argv( 1, arg, sizeof( arg ) );
1380 	i = ClientNumberFromString( ent, arg, qfalse );
1381 	if ( i == -1 ) {
1382 		return;
1383 	}
1384 
1385 	// can't follow self
1386 	if ( &level.clients[ i ] == ent->client ) {
1387 		return;
1388 	}
1389 
1390 	// can't follow another spectator
1391 	if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) {
1392 		return;
1393 	}
1394 
1395 	if ( level.clients[ i ].tempSpectate >= level.time ) {
1396 		return;
1397 	}
1398 
1399 	// if they are playing a tournament game, count as a loss
1400 	if ( (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
1401 		&& ent->client->sess.sessionTeam == TEAM_FREE ) {
1402 		//WTF???
1403 		ent->client->sess.losses++;
1404 	}
1405 
1406 	// first set them to spectator
1407 	if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
1408 		SetTeam( ent, "spectator" );
1409 		// fix: update team switch time only if team change really happend
1410 		if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
1411 			ent->client->switchTeamTime = level.time + 5000;
1412 	}
1413 
1414 	ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
1415 	ent->client->sess.spectatorClient = i;
1416 }
1417 
1418 /*
1419 =================
1420 Cmd_FollowCycle_f
1421 =================
1422 */
Cmd_FollowCycle_f(gentity_t * ent,int dir)1423 void Cmd_FollowCycle_f( gentity_t *ent, int dir ) {
1424 	int		clientnum;
1425 	int		original;
1426 	qboolean	looped = qfalse;
1427 
1428 	if ( ent->client->sess.spectatorState == SPECTATOR_NOT && ent->client->switchTeamTime > level.time ) {
1429 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) );
1430 		return;
1431 	}
1432 
1433 	// if they are playing a tournament game, count as a loss
1434 	if ( (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
1435 		&& ent->client->sess.sessionTeam == TEAM_FREE ) {\
1436 		//WTF???
1437 		ent->client->sess.losses++;
1438 	}
1439 	// first set them to spectator
1440 	if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) {
1441 		SetTeam( ent, "spectator" );
1442 		// fix: update team switch time only if team change really happend
1443 		if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
1444 			ent->client->switchTeamTime = level.time + 5000;
1445 	}
1446 
1447 	if ( dir != 1 && dir != -1 ) {
1448 		trap->Error( ERR_DROP, "Cmd_FollowCycle_f: bad dir %i", dir );
1449 	}
1450 
1451 	clientnum = ent->client->sess.spectatorClient;
1452 	original = clientnum;
1453 
1454 	do {
1455 		clientnum += dir;
1456 		if ( clientnum >= level.maxclients )
1457 		{
1458 			// Avoid /team follow1 crash
1459 			if ( looped )
1460 			{
1461 				clientnum = original;
1462 				break;
1463 			}
1464 			else
1465 			{
1466 				clientnum = 0;
1467 				looped = qtrue;
1468 			}
1469 		}
1470 		if ( clientnum < 0 ) {
1471 			if ( looped )
1472 			{
1473 				clientnum = original;
1474 				break;
1475 			}
1476 			else
1477 			{
1478 				clientnum = level.maxclients - 1;
1479 				looped = qtrue;
1480 			}
1481 		}
1482 
1483 		// can only follow connected clients
1484 		if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) {
1485 			continue;
1486 		}
1487 
1488 		// can't follow another spectator
1489 		if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) {
1490 			continue;
1491 		}
1492 
1493 		// can't follow another spectator
1494 		if ( level.clients[ clientnum ].tempSpectate >= level.time ) {
1495 			return;
1496 		}
1497 
1498 		// this is good, we can use it
1499 		ent->client->sess.spectatorClient = clientnum;
1500 		ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
1501 		return;
1502 	} while ( clientnum != original );
1503 
1504 	// leave it where it was
1505 }
1506 
Cmd_FollowNext_f(gentity_t * ent)1507 void Cmd_FollowNext_f( gentity_t *ent ) {
1508 	Cmd_FollowCycle_f( ent, 1 );
1509 }
1510 
Cmd_FollowPrev_f(gentity_t * ent)1511 void Cmd_FollowPrev_f( gentity_t *ent ) {
1512 	Cmd_FollowCycle_f( ent, -1 );
1513 }
1514 
1515 /*
1516 ==================
1517 G_Say
1518 ==================
1519 */
1520 
G_SayTo(gentity_t * ent,gentity_t * other,int mode,int color,const char * name,const char * message,char * locMsg)1521 static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, char *locMsg )
1522 {
1523 	if (!other) {
1524 		return;
1525 	}
1526 	if (!other->inuse) {
1527 		return;
1528 	}
1529 	if (!other->client) {
1530 		return;
1531 	}
1532 	if ( other->client->pers.connected != CON_CONNECTED ) {
1533 		return;
1534 	}
1535 	if ( mode == SAY_TEAM  && !OnSameTeam(ent, other) ) {
1536 		return;
1537 	}
1538 	/*
1539 	// no chatting to players in tournaments
1540 	if ( (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
1541 		&& other->client->sess.sessionTeam == TEAM_FREE
1542 		&& ent->client->sess.sessionTeam != TEAM_FREE ) {
1543 		//Hmm, maybe some option to do so if allowed?  Or at least in developer mode...
1544 		return;
1545 	}
1546 	*/
1547 	//They've requested I take this out.
1548 
1549 	if (level.gametype == GT_SIEGE &&
1550 		ent->client && (ent->client->tempSpectate >= level.time || ent->client->sess.sessionTeam == TEAM_SPECTATOR) &&
1551 		other->client->sess.sessionTeam != TEAM_SPECTATOR &&
1552 		other->client->tempSpectate < level.time)
1553 	{ //siege temp spectators should not communicate to ingame players
1554 		return;
1555 	}
1556 
1557 	if (locMsg)
1558 	{
1559 		trap->SendServerCommand( other-g_entities, va("%s \"%s\" \"%s\" \"%c\" \"%s\" %i",
1560 			mode == SAY_TEAM ? "ltchat" : "lchat",
1561 			name, locMsg, color, message, ent->s.number));
1562 	}
1563 	else
1564 	{
1565 		trap->SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\" %i",
1566 			mode == SAY_TEAM ? "tchat" : "chat",
1567 			name, Q_COLOR_ESCAPE, color, message, ent->s.number));
1568 	}
1569 }
1570 
G_Say(gentity_t * ent,gentity_t * target,int mode,const char * chatText)1571 void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) {
1572 	int			j;
1573 	gentity_t	*other;
1574 	int			color;
1575 	char		name[64];
1576 	// don't let text be too long for malicious reasons
1577 	char		text[MAX_SAY_TEXT];
1578 	char		location[64];
1579 	char		*locMsg = NULL;
1580 
1581 	if ( level.gametype < GT_TEAM && mode == SAY_TEAM ) {
1582 		mode = SAY_ALL;
1583 	}
1584 
1585 	Q_strncpyz( text, chatText, sizeof(text) );
1586 
1587 	Q_strstrip( text, "\n\r", "  " );
1588 
1589 	switch ( mode ) {
1590 	default:
1591 	case SAY_ALL:
1592 		G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, text );
1593 		Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
1594 		color = COLOR_GREEN;
1595 		break;
1596 	case SAY_TEAM:
1597 		G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, text );
1598 		if (Team_GetLocationMsg(ent, location, sizeof(location)))
1599 		{
1600 			Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ",
1601 				ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
1602 			locMsg = location;
1603 		}
1604 		else
1605 		{
1606 			Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ",
1607 				ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
1608 		}
1609 		color = COLOR_CYAN;
1610 		break;
1611 	case SAY_TELL:
1612 		if (target && target->inuse && target->client && level.gametype >= GT_TEAM &&
1613 			target->client->sess.sessionTeam == ent->client->sess.sessionTeam &&
1614 			Team_GetLocationMsg(ent, location, sizeof(location)))
1615 		{
1616 			Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
1617 			locMsg = location;
1618 		}
1619 		else
1620 		{
1621 			Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
1622 		}
1623 		color = COLOR_MAGENTA;
1624 		break;
1625 	}
1626 
1627 	if ( target ) {
1628 		G_SayTo( ent, target, mode, color, name, text, locMsg );
1629 		return;
1630 	}
1631 
1632 	// echo the text to the console
1633 	if ( dedicated.integer ) {
1634 		trap->Print( "%s%s\n", name, text);
1635 	}
1636 
1637 	// send it to all the appropriate clients
1638 	for (j = 0; j < level.maxclients; j++) {
1639 		other = &g_entities[j];
1640 		G_SayTo( ent, other, mode, color, name, text, locMsg );
1641 	}
1642 }
1643 
1644 
1645 /*
1646 ==================
1647 Cmd_Say_f
1648 ==================
1649 */
Cmd_Say_f(gentity_t * ent)1650 static void Cmd_Say_f( gentity_t *ent ) {
1651 	char *p = NULL;
1652 
1653 	if ( trap->Argc () < 2 )
1654 		return;
1655 
1656 	p = ConcatArgs( 1 );
1657 
1658 	if ( strlen( p ) >= MAX_SAY_TEXT ) {
1659 		p[MAX_SAY_TEXT-1] = '\0';
1660 		G_SecurityLogPrintf( "Cmd_Say_f from %d (%s) has been truncated: %s\n", ent->s.number, ent->client->pers.netname, p );
1661 	}
1662 
1663 	G_Say( ent, NULL, SAY_ALL, p );
1664 }
1665 
1666 /*
1667 ==================
1668 Cmd_SayTeam_f
1669 ==================
1670 */
Cmd_SayTeam_f(gentity_t * ent)1671 static void Cmd_SayTeam_f( gentity_t *ent ) {
1672 	char *p = NULL;
1673 
1674 	if ( trap->Argc () < 2 )
1675 		return;
1676 
1677 	p = ConcatArgs( 1 );
1678 
1679 	if ( strlen( p ) >= MAX_SAY_TEXT ) {
1680 		p[MAX_SAY_TEXT-1] = '\0';
1681 		G_SecurityLogPrintf( "Cmd_SayTeam_f from %d (%s) has been truncated: %s\n", ent->s.number, ent->client->pers.netname, p );
1682 	}
1683 
1684 	G_Say( ent, NULL, (level.gametype>=GT_TEAM) ? SAY_TEAM : SAY_ALL, p );
1685 }
1686 
1687 /*
1688 ==================
1689 Cmd_Tell_f
1690 ==================
1691 */
Cmd_Tell_f(gentity_t * ent)1692 static void Cmd_Tell_f( gentity_t *ent ) {
1693 	int			targetNum;
1694 	gentity_t	*target;
1695 	char		*p;
1696 	char		arg[MAX_TOKEN_CHARS];
1697 
1698 	if ( trap->Argc () < 3 ) {
1699 		trap->SendServerCommand( ent-g_entities, "print \"Usage: tell <player id> <message>\n\"" );
1700 		return;
1701 	}
1702 
1703 	trap->Argv( 1, arg, sizeof( arg ) );
1704 	targetNum = ClientNumberFromString( ent, arg, qfalse );
1705 	if ( targetNum == -1 ) {
1706 		return;
1707 	}
1708 
1709 	target = &g_entities[targetNum];
1710 	if ( !target->inuse || !target->client ) {
1711 		return;
1712 	}
1713 
1714 	p = ConcatArgs( 2 );
1715 
1716 	if ( strlen( p ) >= MAX_SAY_TEXT ) {
1717 		p[MAX_SAY_TEXT-1] = '\0';
1718 		G_SecurityLogPrintf( "Cmd_Tell_f from %d (%s) has been truncated: %s\n", ent->s.number, ent->client->pers.netname, p );
1719 	}
1720 
1721 	G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p );
1722 	G_Say( ent, target, SAY_TELL, p );
1723 	// don't tell to the player self if it was already directed to this player
1724 	// also don't send the chat back to a bot
1725 	if ( ent != target && !(ent->r.svFlags & SVF_BOT)) {
1726 		G_Say( ent, ent, SAY_TELL, p );
1727 	}
1728 }
1729 
1730 //siege voice command
Cmd_VoiceCommand_f(gentity_t * ent)1731 static void Cmd_VoiceCommand_f(gentity_t *ent)
1732 {
1733 	gentity_t *te;
1734 	char arg[MAX_TOKEN_CHARS];
1735 	char *s;
1736 	int i = 0;
1737 
1738 	if (level.gametype < GT_TEAM)
1739 	{
1740 		return;
1741 	}
1742 
1743 	if (trap->Argc() < 2)
1744 	{
1745 		return;
1746 	}
1747 
1748 	if (ent->client->sess.sessionTeam == TEAM_SPECTATOR ||
1749 		ent->client->tempSpectate >= level.time)
1750 	{
1751 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOICECHATASSPEC")) );
1752 		return;
1753 	}
1754 
1755 	trap->Argv(1, arg, sizeof(arg));
1756 
1757 	if (arg[0] == '*')
1758 	{ //hmm.. don't expect a * to be prepended already. maybe someone is trying to be sneaky.
1759 		return;
1760 	}
1761 
1762 	s = va("*%s", arg);
1763 
1764 	//now, make sure it's a valid sound to be playing like this.. so people can't go around
1765 	//screaming out death sounds or whatever.
1766 	while (i < MAX_CUSTOM_SIEGE_SOUNDS)
1767 	{
1768 		if (!bg_customSiegeSoundNames[i])
1769 		{
1770 			break;
1771 		}
1772 		if (!Q_stricmp(bg_customSiegeSoundNames[i], s))
1773 		{ //it matches this one, so it's ok
1774 			break;
1775 		}
1776 		i++;
1777 	}
1778 
1779 	if (i == MAX_CUSTOM_SIEGE_SOUNDS || !bg_customSiegeSoundNames[i])
1780 	{ //didn't find it in the list
1781 		return;
1782 	}
1783 
1784 	te = G_TempEntity(vec3_origin, EV_VOICECMD_SOUND);
1785 	te->s.groundEntityNum = ent->s.number;
1786 	te->s.eventParm = G_SoundIndex((char *)bg_customSiegeSoundNames[i]);
1787 	te->r.svFlags |= SVF_BROADCAST;
1788 }
1789 
1790 
1791 static char	*gc_orders[] = {
1792 	"hold your position",
1793 	"hold this position",
1794 	"come here",
1795 	"cover me",
1796 	"guard location",
1797 	"search and destroy",
1798 	"report"
1799 };
1800 static size_t numgc_orders = ARRAY_LEN( gc_orders );
1801 
Cmd_GameCommand_f(gentity_t * ent)1802 void Cmd_GameCommand_f( gentity_t *ent ) {
1803 	int				targetNum;
1804 	unsigned int	order;
1805 	gentity_t		*target;
1806 	char			arg[MAX_TOKEN_CHARS] = {0};
1807 
1808 	if ( trap->Argc() != 3 ) {
1809 		trap->SendServerCommand( ent-g_entities, va( "print \"Usage: gc <player id> <order 0-%d>\n\"", numgc_orders - 1 ) );
1810 		return;
1811 	}
1812 
1813 	trap->Argv( 2, arg, sizeof( arg ) );
1814 	order = atoi( arg );
1815 
1816 	if ( order >= numgc_orders ) {
1817 		trap->SendServerCommand( ent-g_entities, va("print \"Bad order: %i\n\"", order));
1818 		return;
1819 	}
1820 
1821 	trap->Argv( 1, arg, sizeof( arg ) );
1822 	targetNum = ClientNumberFromString( ent, arg, qfalse );
1823 	if ( targetNum == -1 )
1824 		return;
1825 
1826 	target = &g_entities[targetNum];
1827 	if ( !target->inuse || !target->client )
1828 		return;
1829 
1830 	G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, gc_orders[order] );
1831 	G_Say( ent, target, SAY_TELL, gc_orders[order] );
1832 	// don't tell to the player self if it was already directed to this player
1833 	// also don't send the chat back to a bot
1834 	if ( ent != target && !(ent->r.svFlags & SVF_BOT) )
1835 		G_Say( ent, ent, SAY_TELL, gc_orders[order] );
1836 }
1837 
1838 /*
1839 ==================
1840 Cmd_Where_f
1841 ==================
1842 */
Cmd_Where_f(gentity_t * ent)1843 void Cmd_Where_f( gentity_t *ent ) {
1844 	//JAC: This wasn't working for non-spectators since s.origin doesn't update for active players.
1845 	if(ent->client && ent->client->sess.sessionTeam != TEAM_SPECTATOR )
1846 	{//active players use currentOrigin
1847 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->r.currentOrigin ) ) );
1848 	}
1849 	else
1850 	{
1851 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
1852 	}
1853 	//trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
1854 }
1855 
1856 static const char *gameNames[GT_MAX_GAME_TYPE] = {
1857 	"Free For All",
1858 	"Holocron FFA",
1859 	"Jedi Master",
1860 	"Duel",
1861 	"Power Duel",
1862 	"Single Player",
1863 	"Team FFA",
1864 	"Siege",
1865 	"Capture the Flag",
1866 	"Capture the Ysalamiri"
1867 };
1868 
1869 /*
1870 ==================
1871 Cmd_CallVote_f
1872 ==================
1873 */
1874 extern void SiegeClearSwitchData(void); //g_saga.c
1875 
G_VoteCapturelimit(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)1876 qboolean G_VoteCapturelimit( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
1877 	int n = Com_Clampi( 0, 0x7FFFFFFF, atoi( arg2 ) );
1878 	Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n );
1879 	Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
1880 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
1881 	return qtrue;
1882 }
1883 
G_VoteClientkick(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)1884 qboolean G_VoteClientkick( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
1885 	int n = atoi ( arg2 );
1886 
1887 	if ( n < 0 || n >= level.maxclients ) {
1888 		trap->SendServerCommand( ent-g_entities, va( "print \"invalid client number %d.\n\"", n ) );
1889 		return qfalse;
1890 	}
1891 
1892 	if ( g_entities[n].client->pers.connected == CON_DISCONNECTED ) {
1893 		trap->SendServerCommand( ent-g_entities, va( "print \"there is no client with the client number %d.\n\"", n ) );
1894 		return qfalse;
1895 	}
1896 
1897 	Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 );
1898 	Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, g_entities[n].client->pers.netname );
1899 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
1900 	return qtrue;
1901 }
1902 
G_VoteFraglimit(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)1903 qboolean G_VoteFraglimit( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
1904 	int n = Com_Clampi( 0, 0x7FFFFFFF, atoi( arg2 ) );
1905 	Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n );
1906 	Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
1907 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
1908 	return qtrue;
1909 }
1910 
G_VoteGametype(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)1911 qboolean G_VoteGametype( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
1912 	int gt = atoi( arg2 );
1913 
1914 	// ffa, ctf, tdm, etc
1915 	if ( arg2[0] && isalpha( arg2[0] ) ) {
1916 		gt = BG_GetGametypeForString( arg2 );
1917 		if ( gt == -1 )
1918 		{
1919 			trap->SendServerCommand( ent-g_entities, va( "print \"Gametype (%s) unrecognised, defaulting to FFA/Deathmatch\n\"", arg2 ) );
1920 			gt = GT_FFA;
1921 		}
1922 	}
1923 	// numeric but out of range
1924 	else if ( gt < 0 || gt >= GT_MAX_GAME_TYPE ) {
1925 		trap->SendServerCommand( ent-g_entities, va( "print \"Gametype (%i) is out of range, defaulting to FFA/Deathmatch\n\"", gt ) );
1926 		gt = GT_FFA;
1927 	}
1928 
1929 	// logically invalid gametypes, or gametypes not fully implemented in MP
1930 	if ( gt == GT_SINGLE_PLAYER ) {
1931 		trap->SendServerCommand( ent-g_entities, va( "print \"This gametype is not supported (%s).\n\"", arg2 ) );
1932 		return qfalse;
1933 	}
1934 
1935 	level.votingGametype = qtrue;
1936 	level.votingGametypeTo = gt;
1937 
1938 	Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %d", arg1, gt );
1939 	Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, gameNames[gt] );
1940 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
1941 	return qtrue;
1942 }
1943 
G_VoteKick(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)1944 qboolean G_VoteKick( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
1945 	int clientid = ClientNumberFromString( ent, arg2, qtrue );
1946 	gentity_t *target = NULL;
1947 
1948 	if ( clientid == -1 )
1949 		return qfalse;
1950 
1951 	target = &g_entities[clientid];
1952 	if ( !target || !target->inuse || !target->client )
1953 		return qfalse;
1954 
1955 	Com_sprintf( level.voteString, sizeof( level.voteString ), "clientkick %d", clientid );
1956 	Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "kick %s", target->client->pers.netname );
1957 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
1958 	return qtrue;
1959 }
1960 
1961 const char *G_GetArenaInfoByMap( const char *map );
1962 
Cmd_MapList_f(gentity_t * ent)1963 void Cmd_MapList_f( gentity_t *ent ) {
1964 	int i, toggle=0;
1965 	char map[24] = "--", buf[512] = {0};
1966 
1967 	Q_strcat( buf, sizeof( buf ), "Map list:" );
1968 
1969 	for ( i=0; i<level.arenas.num; i++ ) {
1970 		Q_strncpyz( map, Info_ValueForKey( level.arenas.infos[i], "map" ), sizeof( map ) );
1971 		Q_StripColor( map );
1972 
1973 		if ( G_DoesMapSupportGametype( map, level.gametype ) ) {
1974 			char *tmpMsg = va( " ^%c%s", (++toggle&1) ? COLOR_GREEN : COLOR_YELLOW, map );
1975 			if ( strlen( buf ) + strlen( tmpMsg ) >= sizeof( buf ) ) {
1976 				trap->SendServerCommand( ent-g_entities, va( "print \"%s\"", buf ) );
1977 				buf[0] = '\0';
1978 			}
1979 			Q_strcat( buf, sizeof( buf ), tmpMsg );
1980 		}
1981 	}
1982 
1983 	trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", buf ) );
1984 }
1985 
G_VoteMap(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)1986 qboolean G_VoteMap( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
1987 	char s[MAX_CVAR_VALUE_STRING] = {0}, bspName[MAX_QPATH] = {0}, *mapName = NULL, *mapName2 = NULL;
1988 	fileHandle_t fp = NULL_FILE;
1989 	const char *arenaInfo;
1990 
1991 	// didn't specify a map, show available maps
1992 	if ( numArgs < 3 ) {
1993 		Cmd_MapList_f( ent );
1994 		return qfalse;
1995 	}
1996 
1997 	if ( strchr( arg2, '\\' ) ) {
1998 		trap->SendServerCommand( ent-g_entities, "print \"Can't have mapnames with a \\\n\"" );
1999 		return qfalse;
2000 	}
2001 
2002 	Com_sprintf( bspName, sizeof(bspName), "maps/%s.bsp", arg2 );
2003 	if ( trap->FS_Open( bspName, &fp, FS_READ ) <= 0 ) {
2004 		trap->SendServerCommand( ent-g_entities, va( "print \"Can't find map %s on server\n\"", bspName ) );
2005 		if( fp != NULL_FILE )
2006 			trap->FS_Close( fp );
2007 		return qfalse;
2008 	}
2009 	trap->FS_Close( fp );
2010 
2011 	if ( !G_DoesMapSupportGametype( arg2, level.gametype ) ) {
2012 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOVOTE_MAPNOTSUPPORTEDBYGAME" ) ) );
2013 		return qfalse;
2014 	}
2015 
2016 	// preserve the map rotation
2017 	trap->Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) );
2018 	if ( *s )
2019 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s; set nextmap \"%s\"", arg1, arg2, s );
2020 	else
2021 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 );
2022 
2023 	arenaInfo = G_GetArenaInfoByMap(arg2);
2024 	if ( arenaInfo ) {
2025 		mapName = Info_ValueForKey( arenaInfo, "longname" );
2026 		mapName2 = Info_ValueForKey( arenaInfo, "map" );
2027 	}
2028 
2029 	if ( !mapName || !mapName[0] )
2030 		mapName = "ERROR";
2031 
2032 	if ( !mapName2 || !mapName2[0] )
2033 		mapName2 = "ERROR";
2034 
2035 	Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "map %s (%s)", mapName, mapName2 );
2036 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
2037 	return qtrue;
2038 }
2039 
G_VoteMapRestart(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)2040 qboolean G_VoteMapRestart( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
2041 	int n = Com_Clampi( 0, 60, atoi( arg2 ) );
2042 	if ( numArgs < 3 )
2043 		n = 5;
2044 	Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n );
2045 	Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) );
2046 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
2047 	return qtrue;
2048 }
2049 
G_VoteNextmap(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)2050 qboolean G_VoteNextmap( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
2051 	char s[MAX_CVAR_VALUE_STRING];
2052 
2053 	trap->Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) );
2054 	if ( !*s ) {
2055 		trap->SendServerCommand( ent-g_entities, "print \"nextmap not set.\n\"" );
2056 		return qfalse;
2057 	}
2058 	SiegeClearSwitchData();
2059 	Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap");
2060 	Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) );
2061 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
2062 	return qtrue;
2063 }
2064 
G_VoteTimelimit(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)2065 qboolean G_VoteTimelimit( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
2066 	float tl = Com_Clamp( 0.0f, 35790.0f, atof( arg2 ) );
2067 	if ( Q_isintegral( tl ) )
2068 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, (int)tl );
2069 	else
2070 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %.3f", arg1, tl );
2071 	Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) );
2072 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
2073 	return qtrue;
2074 }
2075 
G_VoteWarmup(gentity_t * ent,int numArgs,const char * arg1,const char * arg2)2076 qboolean G_VoteWarmup( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) {
2077 	int n = Com_Clampi( 0, 1, atoi( arg2 ) );
2078 	Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n );
2079 	Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) );
2080 	Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
2081 	return qtrue;
2082 }
2083 
2084 typedef struct voteString_s {
2085 	const char	*string;
2086 	const char	*aliases;	// space delimited list of aliases, will always show the real vote string
2087 	qboolean	(*func)(gentity_t *ent, int numArgs, const char *arg1, const char *arg2);
2088 	int			numArgs;	// number of REQUIRED arguments, not total/optional arguments
2089 	uint32_t	validGT;	// bit-flag of valid gametypes
2090 	qboolean	voteDelay;	// if true, will delay executing the vote string after it's accepted by g_voteDelay
2091 	const char	*shortHelp;	// NULL if no arguments needed
2092 } voteString_t;
2093 
2094 static voteString_t validVoteStrings[] = {
2095 	//	vote string				aliases										# args	valid gametypes							exec delay		short help
2096 	{	"capturelimit",			"caps",				G_VoteCapturelimit,		1,		GTB_CTF|GTB_CTY,						qtrue,			"<num>" },
2097 	{	"clientkick",			NULL,				G_VoteClientkick,		1,		GTB_ALL,								qfalse,			"<clientnum>" },
2098 	{	"fraglimit",			"frags",			G_VoteFraglimit,		1,		GTB_ALL & ~(GTB_SIEGE|GTB_CTF|GTB_CTY),	qtrue,			"<num>" },
2099 	{	"g_doWarmup",			"dowarmup warmup",	G_VoteWarmup,			1,		GTB_ALL,								qtrue,			"<0-1>" },
2100 	{	"g_gametype",			"gametype gt mode",	G_VoteGametype,			1,		GTB_ALL,								qtrue,			"<num or name>" },
2101 	{	"kick",					NULL,				G_VoteKick,				1,		GTB_ALL,								qfalse,			"<client name>" },
2102 	{	"map",					NULL,				G_VoteMap,				0,		GTB_ALL,								qtrue,			"<name>" },
2103 	{	"map_restart",			"restart",			G_VoteMapRestart,		0,		GTB_ALL,								qtrue,			"<optional delay>" },
2104 	{	"nextmap",				NULL,				G_VoteNextmap,			0,		GTB_ALL,								qtrue,			NULL },
2105 	{	"timelimit",			"time",				G_VoteTimelimit,		1,		GTB_ALL &~GTB_SIEGE,					qtrue,			"<num>" },
2106 };
2107 static const int validVoteStringsSize = ARRAY_LEN( validVoteStrings );
2108 
Svcmd_ToggleAllowVote_f(void)2109 void Svcmd_ToggleAllowVote_f( void ) {
2110 	if ( trap->Argc() == 1 ) {
2111 		int i = 0;
2112 		for ( i = 0; i<validVoteStringsSize; i++ ) {
2113 			if ( (g_allowVote.integer & (1 << i)) )	trap->Print( "%2d [X] %s\n", i, validVoteStrings[i].string );
2114 			else									trap->Print( "%2d [ ] %s\n", i, validVoteStrings[i].string );
2115 		}
2116 		return;
2117 	}
2118 	else {
2119 		char arg[8] = { 0 };
2120 		int index;
2121 
2122 		trap->Argv( 1, arg, sizeof( arg ) );
2123 		index = atoi( arg );
2124 
2125 		if ( index < 0 || index >= validVoteStringsSize ) {
2126 			Com_Printf( "ToggleAllowVote: Invalid range: %i [0, %i]\n", index, validVoteStringsSize - 1 );
2127 			return;
2128 		}
2129 
2130 		trap->Cvar_Set( "g_allowVote", va( "%i", (1 << index) ^ (g_allowVote.integer & ((1 << validVoteStringsSize) - 1)) ) );
2131 		trap->Cvar_Update( &g_allowVote );
2132 
2133 		Com_Printf( "%s %s^7\n", validVoteStrings[index].string, ((g_allowVote.integer & (1 << index)) ? "^2Enabled" : "^1Disabled") );
2134 	}
2135 }
2136 
Cmd_CallVote_f(gentity_t * ent)2137 void Cmd_CallVote_f( gentity_t *ent ) {
2138 	int				i=0, numArgs=0;
2139 	char			arg1[MAX_CVAR_VALUE_STRING] = {0};
2140 	char			arg2[MAX_CVAR_VALUE_STRING] = {0};
2141 	voteString_t	*vote = NULL;
2142 
2143 	// not allowed to vote at all
2144 	if ( !g_allowVote.integer ) {
2145 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOVOTE" ) ) );
2146 		return;
2147 	}
2148 
2149 	// vote in progress
2150 	else if ( level.voteTime ) {
2151 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "VOTEINPROGRESS" ) ) );
2152 		return;
2153 	}
2154 
2155 	// can't vote as a spectator, except in (power)duel
2156 	else if ( level.gametype != GT_DUEL && level.gametype != GT_POWERDUEL && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
2157 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOSPECVOTE" ) ) );
2158 		return;
2159 	}
2160 
2161 	// make sure it is a valid command to vote on
2162 	numArgs = trap->Argc();
2163 	trap->Argv( 1, arg1, sizeof( arg1 ) );
2164 	if ( numArgs > 1 )
2165 		Q_strncpyz( arg2, ConcatArgs( 2 ), sizeof( arg2 ) );
2166 
2167 	// filter ; \n \r
2168 	if ( Q_strchrs( arg1, ";\r\n" ) || Q_strchrs( arg2, ";\r\n" ) ) {
2169 		trap->SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
2170 		return;
2171 	}
2172 
2173 	// check for invalid votes
2174 	for ( i=0; i<validVoteStringsSize; i++ ) {
2175 		if ( !(g_allowVote.integer & (1<<i)) )
2176 			continue;
2177 
2178 		if ( !Q_stricmp( arg1, validVoteStrings[i].string ) )
2179 			break;
2180 
2181 		// see if they're using an alias, and set arg1 to the actual vote string
2182 		if ( validVoteStrings[i].aliases ) {
2183 			char tmp[MAX_TOKEN_CHARS] = {0}, *p = NULL;
2184 			const char *delim = " ";
2185 			Q_strncpyz( tmp, validVoteStrings[i].aliases, sizeof( tmp ) );
2186 			p = strtok( tmp, delim );
2187 			while ( p != NULL ) {
2188 				if ( !Q_stricmp( arg1, p ) ) {
2189 					Q_strncpyz( arg1, validVoteStrings[i].string, sizeof( arg1 ) );
2190 					goto validVote;
2191 				}
2192 				p = strtok( NULL, delim );
2193 			}
2194 		}
2195 	}
2196 	// invalid vote string, abandon ship
2197 	if ( i == validVoteStringsSize ) {
2198 		char buf[1024] = {0};
2199 		int toggle = 0;
2200 		trap->SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
2201 		trap->SendServerCommand( ent-g_entities, "print \"Allowed vote strings are: \"" );
2202 		for ( i=0; i<validVoteStringsSize; i++ ) {
2203 			if ( !(g_allowVote.integer & (1<<i)) )
2204 				continue;
2205 
2206 			toggle = !toggle;
2207 			if ( validVoteStrings[i].shortHelp ) {
2208 				Q_strcat( buf, sizeof( buf ), va( "^%c%s %s ",
2209 					toggle ? COLOR_GREEN : COLOR_YELLOW,
2210 					validVoteStrings[i].string,
2211 					validVoteStrings[i].shortHelp ) );
2212 			}
2213 			else {
2214 				Q_strcat( buf, sizeof( buf ), va( "^%c%s ",
2215 					toggle ? COLOR_GREEN : COLOR_YELLOW,
2216 					validVoteStrings[i].string ) );
2217 			}
2218 		}
2219 
2220 		//FIXME: buffer and send in multiple messages in case of overflow
2221 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", buf ) );
2222 		return;
2223 	}
2224 
2225 validVote:
2226 	vote = &validVoteStrings[i];
2227 	if ( !(vote->validGT & (1<<level.gametype)) ) {
2228 		trap->SendServerCommand( ent-g_entities, va( "print \"%s is not applicable in this gametype.\n\"", arg1 ) );
2229 		return;
2230 	}
2231 
2232 	if ( numArgs < vote->numArgs+2 ) {
2233 		trap->SendServerCommand( ent-g_entities, va( "print \"%s requires more arguments: %s\n\"", arg1, vote->shortHelp ) );
2234 		return;
2235 	}
2236 
2237 	level.votingGametype = qfalse;
2238 
2239 	level.voteExecuteDelay = vote->voteDelay ? g_voteDelay.integer : 0;
2240 
2241 	// there is still a vote to be executed, execute it and store the new vote
2242 	if ( level.voteExecuteTime ) {
2243 		level.voteExecuteTime = 0;
2244 		trap->SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
2245 	}
2246 
2247 	// pass the args onto vote-specific handlers for parsing/filtering
2248 	if ( vote->func ) {
2249 		if ( !vote->func( ent, numArgs, arg1, arg2 ) )
2250 			return;
2251 	}
2252 	// otherwise assume it's a command
2253 	else {
2254 		Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 );
2255 		Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) );
2256 		Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) );
2257 	}
2258 	Q_strstrip( level.voteStringClean, "\"\n\r", NULL );
2259 
2260 	trap->SendServerCommand( -1, va( "print \"%s^7 %s (%s)\n\"", ent->client->pers.netname, G_GetStringEdString( "MP_SVGAME", "PLCALLEDVOTE" ), level.voteStringClean ) );
2261 
2262 	// start the voting, the caller automatically votes yes
2263 	level.voteTime = level.time;
2264 	level.voteYes = 1;
2265 	level.voteNo = 0;
2266 
2267 	for ( i=0; i<level.maxclients; i++ ) {
2268 		level.clients[i].mGameFlags &= ~PSG_VOTED;
2269 		level.clients[i].pers.vote = 0;
2270 	}
2271 
2272 	ent->client->mGameFlags |= PSG_VOTED;
2273 	ent->client->pers.vote = 1;
2274 
2275 	trap->SetConfigstring( CS_VOTE_TIME,	va( "%i", level.voteTime ) );
2276 	trap->SetConfigstring( CS_VOTE_STRING,	level.voteDisplayString );
2277 	trap->SetConfigstring( CS_VOTE_YES,		va( "%i", level.voteYes ) );
2278 	trap->SetConfigstring( CS_VOTE_NO,		va( "%i", level.voteNo ) );
2279 }
2280 
2281 /*
2282 ==================
2283 Cmd_Vote_f
2284 ==================
2285 */
Cmd_Vote_f(gentity_t * ent)2286 void Cmd_Vote_f( gentity_t *ent ) {
2287 	char		msg[64] = {0};
2288 
2289 	if ( !level.voteTime ) {
2290 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTEINPROG")) );
2291 		return;
2292 	}
2293 	if ( ent->client->mGameFlags & PSG_VOTED ) {
2294 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "VOTEALREADY")) );
2295 		return;
2296 	}
2297 	if (level.gametype != GT_DUEL && level.gametype != GT_POWERDUEL)
2298 	{
2299 		if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
2300 			trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTEASSPEC")) );
2301 			return;
2302 		}
2303 	}
2304 
2305 	trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLVOTECAST")) );
2306 
2307 	ent->client->mGameFlags |= PSG_VOTED;
2308 
2309 	trap->Argv( 1, msg, sizeof( msg ) );
2310 
2311 	if ( tolower( msg[0] ) == 'y' || msg[0] == '1' ) {
2312 		level.voteYes++;
2313 		ent->client->pers.vote = 1;
2314 		trap->SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) );
2315 	} else {
2316 		level.voteNo++;
2317 		ent->client->pers.vote = 2;
2318 		trap->SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) );
2319 	}
2320 
2321 	// a majority will be determined in CheckVote, which will also account
2322 	// for players entering or leaving
2323 }
2324 
G_TeamVoteLeader(gentity_t * ent,int cs_offset,team_t team,int numArgs,const char * arg1,const char * arg2)2325 qboolean G_TeamVoteLeader( gentity_t *ent, int cs_offset, team_t team, int numArgs, const char *arg1, const char *arg2 ) {
2326 	int clientid = numArgs == 2 ? ent->s.number : ClientNumberFromString( ent, arg2, qfalse );
2327 	gentity_t *target = NULL;
2328 
2329 	if ( clientid == -1 )
2330 		return qfalse;
2331 
2332 	target = &g_entities[clientid];
2333 	if ( !target || !target->inuse || !target->client )
2334 		return qfalse;
2335 
2336 	if ( target->client->sess.sessionTeam != team )
2337 	{
2338 		trap->SendServerCommand( ent-g_entities, va( "print \"User %s is not on your team\n\"", arg2 ) );
2339 		return qfalse;
2340 	}
2341 
2342 	Com_sprintf( level.teamVoteString[cs_offset], sizeof( level.teamVoteString[cs_offset] ), "leader %d", clientid );
2343 	Q_strncpyz( level.teamVoteDisplayString[cs_offset], level.teamVoteString[cs_offset], sizeof( level.teamVoteDisplayString[cs_offset] ) );
2344 	Q_strncpyz( level.teamVoteStringClean[cs_offset], level.teamVoteString[cs_offset], sizeof( level.teamVoteStringClean[cs_offset] ) );
2345 	return qtrue;
2346 }
2347 
2348 /*
2349 ==================
2350 Cmd_CallTeamVote_f
2351 ==================
2352 */
Cmd_CallTeamVote_f(gentity_t * ent)2353 void Cmd_CallTeamVote_f( gentity_t *ent ) {
2354 	team_t	team = ent->client->sess.sessionTeam;
2355 	int		i=0, cs_offset=0, numArgs=0;
2356 	char	arg1[MAX_CVAR_VALUE_STRING] = {0};
2357 	char	arg2[MAX_CVAR_VALUE_STRING] = {0};
2358 
2359 	if ( team == TEAM_RED )
2360 		cs_offset = 0;
2361 	else if ( team == TEAM_BLUE )
2362 		cs_offset = 1;
2363 	else
2364 		return;
2365 
2366 	// not allowed to vote at all
2367 	if ( !g_allowTeamVote.integer ) {
2368 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTE")) );
2369 		return;
2370 	}
2371 
2372 	// vote in progress
2373 	else if ( level.teamVoteTime[cs_offset] ) {
2374 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TEAMVOTEALREADY")) );
2375 		return;
2376 	}
2377 
2378 	// can't vote as a spectator
2379 	else if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
2380 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSPECVOTE")) );
2381 		return;
2382 	}
2383 
2384 	// make sure it is a valid command to vote on
2385 	numArgs = trap->Argc();
2386 	trap->Argv( 1, arg1, sizeof( arg1 ) );
2387 	if ( numArgs > 1 )
2388 		Q_strncpyz( arg2, ConcatArgs( 2 ), sizeof( arg2 ) );
2389 
2390 	// filter ; \n \r
2391 	if ( Q_strchrs( arg1, ";\r\n" ) || Q_strchrs( arg2, ";\r\n" ) ) {
2392 		trap->SendServerCommand( ent-g_entities, "print \"Invalid team vote string.\n\"" );
2393 		return;
2394 	}
2395 
2396 	// pass the args onto vote-specific handlers for parsing/filtering
2397 	if ( !Q_stricmp( arg1, "leader" ) ) {
2398 		if ( !G_TeamVoteLeader( ent, cs_offset, team, numArgs, arg1, arg2 ) )
2399 			return;
2400 	}
2401 	else {
2402 		trap->SendServerCommand( ent-g_entities, "print \"Invalid team vote string.\n\"" );
2403 		trap->SendServerCommand( ent-g_entities, va("print \"Allowed team vote strings are: ^%c%s %s\n\"", COLOR_GREEN, "leader", "<optional client name or number>" ));
2404 		return;
2405 	}
2406 
2407 	Q_strstrip( level.teamVoteStringClean[cs_offset], "\"\n\r", NULL );
2408 
2409 	for ( i=0; i<level.maxclients; i++ ) {
2410 		if ( level.clients[i].pers.connected == CON_DISCONNECTED )
2411 			continue;
2412 		if ( level.clients[i].sess.sessionTeam == team )
2413 			trap->SendServerCommand( i, va("print \"%s^7 called a team vote (%s)\n\"", ent->client->pers.netname, level.teamVoteStringClean[cs_offset] ) );
2414 	}
2415 
2416 	// start the voting, the caller autoamtically votes yes
2417 	level.teamVoteTime[cs_offset] = level.time;
2418 	level.teamVoteYes[cs_offset] = 1;
2419 	level.teamVoteNo[cs_offset] = 0;
2420 
2421 	for ( i=0; i<level.maxclients; i++ ) {
2422 		if ( level.clients[i].pers.connected == CON_DISCONNECTED )
2423 			continue;
2424 		if ( level.clients[i].sess.sessionTeam == team ) {
2425 			level.clients[i].mGameFlags &= ~PSG_TEAMVOTED;
2426 			level.clients[i].pers.teamvote = 0;
2427 		}
2428 	}
2429 	ent->client->mGameFlags |= PSG_TEAMVOTED;
2430 	ent->client->pers.teamvote = 1;
2431 
2432 	trap->SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va("%i", level.teamVoteTime[cs_offset] ) );
2433 	trap->SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteDisplayString[cs_offset] );
2434 	trap->SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) );
2435 	trap->SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) );
2436 }
2437 
2438 /*
2439 ==================
2440 Cmd_TeamVote_f
2441 ==================
2442 */
Cmd_TeamVote_f(gentity_t * ent)2443 void Cmd_TeamVote_f( gentity_t *ent ) {
2444 	team_t		team = ent->client->sess.sessionTeam;
2445 	int			cs_offset=0;
2446 	char		msg[64] = {0};
2447 
2448 	if ( team == TEAM_RED )
2449 		cs_offset = 0;
2450 	else if ( team == TEAM_BLUE )
2451 		cs_offset = 1;
2452 	else
2453 		return;
2454 
2455 	if ( !level.teamVoteTime[cs_offset] ) {
2456 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOTEAMVOTEINPROG")) );
2457 		return;
2458 	}
2459 	if ( ent->client->mGameFlags & PSG_TEAMVOTED ) {
2460 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TEAMVOTEALREADYCAST")) );
2461 		return;
2462 	}
2463 	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
2464 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTEASSPEC")) );
2465 		return;
2466 	}
2467 
2468 	trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLTEAMVOTECAST")) );
2469 
2470 	ent->client->mGameFlags |= PSG_TEAMVOTED;
2471 
2472 	trap->Argv( 1, msg, sizeof( msg ) );
2473 
2474 	if ( tolower( msg[0] ) == 'y' || msg[0] == '1' ) {
2475 		level.teamVoteYes[cs_offset]++;
2476 		ent->client->pers.teamvote = 1;
2477 		trap->SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) );
2478 	} else {
2479 		level.teamVoteNo[cs_offset]++;
2480 		ent->client->pers.teamvote = 2;
2481 		trap->SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) );
2482 	}
2483 
2484 	// a majority will be determined in TeamCheckVote, which will also account
2485 	// for players entering or leaving
2486 }
2487 
2488 
2489 /*
2490 =================
2491 Cmd_SetViewpos_f
2492 =================
2493 */
Cmd_SetViewpos_f(gentity_t * ent)2494 void Cmd_SetViewpos_f( gentity_t *ent ) {
2495 	vec3_t		origin, angles;
2496 	char		buffer[MAX_TOKEN_CHARS];
2497 	int			i;
2498 
2499 	if ( trap->Argc() != 5 ) {
2500 		trap->SendServerCommand( ent-g_entities, va("print \"usage: setviewpos x y z yaw\n\""));
2501 		return;
2502 	}
2503 
2504 	VectorClear( angles );
2505 	for ( i = 0 ; i < 3 ; i++ ) {
2506 		trap->Argv( i + 1, buffer, sizeof( buffer ) );
2507 		origin[i] = atof( buffer );
2508 	}
2509 
2510 	trap->Argv( 4, buffer, sizeof( buffer ) );
2511 	angles[YAW] = atof( buffer );
2512 
2513 	TeleportPlayer( ent, origin, angles );
2514 }
2515 
G_LeaveVehicle(gentity_t * ent,qboolean ConCheck)2516 void G_LeaveVehicle( gentity_t* ent, qboolean ConCheck ) {
2517 
2518 	if (ent->client->ps.m_iVehicleNum)
2519 	{ //tell it I'm getting off
2520 		gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum];
2521 
2522 		if (veh->inuse && veh->client && veh->m_pVehicle)
2523 		{
2524 			if ( ConCheck ) { // check connection
2525 				clientConnected_t pCon = ent->client->pers.connected;
2526 				ent->client->pers.connected = CON_DISCONNECTED;
2527 				veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue);
2528 				ent->client->pers.connected = pCon;
2529 			} else { // or not.
2530 				veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue);
2531 			}
2532 		}
2533 	}
2534 
2535 	ent->client->ps.m_iVehicleNum = 0;
2536 }
2537 
G_ItemUsable(playerState_t * ps,int forcedUse)2538 int G_ItemUsable(playerState_t *ps, int forcedUse)
2539 {
2540 	vec3_t fwd, fwdorg, dest, pos;
2541 	vec3_t yawonly;
2542 	vec3_t mins, maxs;
2543 	vec3_t trtest;
2544 	trace_t tr;
2545 
2546 	// fix: dead players shouldn't use items
2547 	if (ps->stats[STAT_HEALTH] <= 0) {
2548 		return 0;
2549 	}
2550 
2551 	if (ps->m_iVehicleNum)
2552 	{
2553 		return 0;
2554 	}
2555 
2556 	if (ps->pm_flags & PMF_USE_ITEM_HELD)
2557 	{ //force to let go first
2558 		return 0;
2559 	}
2560 
2561 	if (!forcedUse)
2562 	{
2563 		forcedUse = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag;
2564 	}
2565 
2566 	if (!BG_IsItemSelectable(ps, forcedUse))
2567 	{
2568 		return 0;
2569 	}
2570 
2571 	switch (forcedUse)
2572 	{
2573 	case HI_MEDPAC:
2574 	case HI_MEDPAC_BIG:
2575 		if (ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH])
2576 		{
2577 			return 0;
2578 		}
2579 
2580 		if (ps->stats[STAT_HEALTH] <= 0)
2581 		{
2582 			return 0;
2583 		}
2584 
2585 		return 1;
2586 	case HI_SEEKER:
2587 		if (ps->eFlags & EF_SEEKERDRONE)
2588 		{
2589 			G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SEEKER_ALREADYDEPLOYED);
2590 			return 0;
2591 		}
2592 
2593 		return 1;
2594 	case HI_SENTRY_GUN:
2595 		if (ps->fd.sentryDeployed)
2596 		{
2597 			G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SENTRY_ALREADYPLACED);
2598 			return 0;
2599 		}
2600 
2601 		yawonly[ROLL] = 0;
2602 		yawonly[PITCH] = 0;
2603 		yawonly[YAW] = ps->viewangles[YAW];
2604 
2605 		VectorSet( mins, -8, -8, 0 );
2606 		VectorSet( maxs, 8, 8, 24 );
2607 
2608 		AngleVectors(yawonly, fwd, NULL, NULL);
2609 
2610 		fwdorg[0] = ps->origin[0] + fwd[0]*64;
2611 		fwdorg[1] = ps->origin[1] + fwd[1]*64;
2612 		fwdorg[2] = ps->origin[2] + fwd[2]*64;
2613 
2614 		trtest[0] = fwdorg[0] + fwd[0]*16;
2615 		trtest[1] = fwdorg[1] + fwd[1]*16;
2616 		trtest[2] = fwdorg[2] + fwd[2]*16;
2617 
2618 		trap->Trace(&tr, ps->origin, mins, maxs, trtest, ps->clientNum, MASK_PLAYERSOLID, qfalse, 0, 0);
2619 
2620 		if ((tr.fraction != 1 && tr.entityNum != ps->clientNum) || tr.startsolid || tr.allsolid)
2621 		{
2622 			G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SENTRY_NOROOM);
2623 			return 0;
2624 		}
2625 
2626 		return 1;
2627 	case HI_SHIELD:
2628 		mins[0] = -8;
2629 		mins[1] = -8;
2630 		mins[2] = 0;
2631 
2632 		maxs[0] = 8;
2633 		maxs[1] = 8;
2634 		maxs[2] = 8;
2635 
2636 		AngleVectors (ps->viewangles, fwd, NULL, NULL);
2637 		fwd[2] = 0;
2638 		VectorMA(ps->origin, 64, fwd, dest);
2639 		trap->Trace(&tr, ps->origin, mins, maxs, dest, ps->clientNum, MASK_SHOT, qfalse, 0, 0 );
2640 		if (tr.fraction > 0.9 && !tr.startsolid && !tr.allsolid)
2641 		{
2642 			VectorCopy(tr.endpos, pos);
2643 			VectorSet( dest, pos[0], pos[1], pos[2] - 4096 );
2644 			trap->Trace( &tr, pos, mins, maxs, dest, ps->clientNum, MASK_SOLID, qfalse, 0, 0 );
2645 			if ( !tr.startsolid && !tr.allsolid )
2646 			{
2647 				return 1;
2648 			}
2649 		}
2650 		G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SHIELD_NOROOM);
2651 		return 0;
2652 	case HI_JETPACK: //do something?
2653 		return 1;
2654 	case HI_HEALTHDISP:
2655 		return 1;
2656 	case HI_AMMODISP:
2657 		return 1;
2658 	case HI_EWEB:
2659 		return 1;
2660 	case HI_CLOAK:
2661 		return 1;
2662 	default:
2663 		return 1;
2664 	}
2665 }
2666 
2667 void saberKnockDown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
2668 
Cmd_ToggleSaber_f(gentity_t * ent)2669 void Cmd_ToggleSaber_f(gentity_t *ent)
2670 {
2671 	if (ent->client->ps.fd.forceGripCripple)
2672 	{ //if they are being gripped, don't let them unholster their saber
2673 		if (ent->client->ps.saberHolstered)
2674 		{
2675 			return;
2676 		}
2677 	}
2678 
2679 	if (ent->client->ps.saberInFlight)
2680 	{
2681 		if (ent->client->ps.saberEntityNum)
2682 		{ //turn it off in midair
2683 			saberKnockDown(&g_entities[ent->client->ps.saberEntityNum], ent, ent);
2684 		}
2685 		return;
2686 	}
2687 
2688 	if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
2689 	{
2690 		return;
2691 	}
2692 
2693 	if (ent->client->ps.weapon != WP_SABER)
2694 	{
2695 		return;
2696 	}
2697 
2698 //	if (ent->client->ps.duelInProgress && !ent->client->ps.saberHolstered)
2699 //	{
2700 //		return;
2701 //	}
2702 
2703 	if (ent->client->ps.duelTime >= level.time)
2704 	{
2705 		return;
2706 	}
2707 
2708 	if (ent->client->ps.saberLockTime >= level.time)
2709 	{
2710 		return;
2711 	}
2712 
2713 	if (ent->client && ent->client->ps.weaponTime < 1)
2714 	{
2715 		if (ent->client->ps.saberHolstered == 2)
2716 		{
2717 			ent->client->ps.saberHolstered = 0;
2718 
2719 			if (ent->client->saber[0].soundOn)
2720 			{
2721 				G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn);
2722 			}
2723 			if (ent->client->saber[1].soundOn)
2724 			{
2725 				G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn);
2726 			}
2727 		}
2728 		else
2729 		{
2730 			ent->client->ps.saberHolstered = 2;
2731 			if (ent->client->saber[0].soundOff)
2732 			{
2733 				G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOff);
2734 			}
2735 			if (ent->client->saber[1].soundOff &&
2736 				ent->client->saber[1].model[0])
2737 			{
2738 				G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff);
2739 			}
2740 			//prevent anything from being done for 400ms after holster
2741 			ent->client->ps.weaponTime = 400;
2742 		}
2743 	}
2744 }
2745 
2746 extern vmCvar_t		d_saberStanceDebug;
2747 
2748 extern qboolean WP_SaberCanTurnOffSomeBlades( saberInfo_t *saber );
Cmd_SaberAttackCycle_f(gentity_t * ent)2749 void Cmd_SaberAttackCycle_f(gentity_t *ent)
2750 {
2751 	int selectLevel = 0;
2752 	qboolean usingSiegeStyle = qfalse;
2753 
2754 	if ( !ent || !ent->client )
2755 	{
2756 		return;
2757 	}
2758 
2759 	if ( level.intermissionQueued || level.intermissiontime )
2760 	{
2761 		trap->SendServerCommand( ent-g_entities, va( "print \"%s (saberAttackCycle)\n\"", G_GetStringEdString( "MP_SVGAME", "CANNOT_TASK_INTERMISSION" ) ) );
2762 		return;
2763 	}
2764 
2765 	if ( ent->health <= 0
2766 			|| ent->client->tempSpectate >= level.time
2767 			|| ent->client->sess.sessionTeam == TEAM_SPECTATOR )
2768 	{
2769 		trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) );
2770 		return;
2771 	}
2772 
2773 
2774 	if ( ent->client->ps.weapon != WP_SABER )
2775 	{
2776         return;
2777 	}
2778 	/*
2779 	if (ent->client->ps.weaponTime > 0)
2780 	{ //no switching attack level when busy
2781 		return;
2782 	}
2783 	*/
2784 
2785 	if (ent->client->saber[0].model[0] && ent->client->saber[1].model[0])
2786 	{ //no cycling for akimbo
2787 		if ( WP_SaberCanTurnOffSomeBlades( &ent->client->saber[1] ) )
2788 		{//can turn second saber off
2789 			if ( ent->client->ps.saberHolstered == 1 )
2790 			{//have one holstered
2791 				//unholster it
2792 				G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn);
2793 				ent->client->ps.saberHolstered = 0;
2794 				//g_active should take care of this, but...
2795 				ent->client->ps.fd.saberAnimLevel = SS_DUAL;
2796 			}
2797 			else if ( ent->client->ps.saberHolstered == 0 )
2798 			{//have none holstered
2799 				if ( (ent->client->saber[1].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE) )
2800 				{//can't turn it off manually
2801 				}
2802 				else if ( ent->client->saber[1].bladeStyle2Start > 0
2803 					&& (ent->client->saber[1].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE2) )
2804 				{//can't turn it off manually
2805 				}
2806 				else
2807 				{
2808 					//turn it off
2809 					G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff);
2810 					ent->client->ps.saberHolstered = 1;
2811 					//g_active should take care of this, but...
2812 					ent->client->ps.fd.saberAnimLevel = SS_FAST;
2813 				}
2814 			}
2815 
2816 			if (d_saberStanceDebug.integer)
2817 			{
2818 				trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to toggle dual saber blade.\n\"") );
2819 			}
2820 			return;
2821 		}
2822 	}
2823 	else if (ent->client->saber[0].numBlades > 1
2824 		&& WP_SaberCanTurnOffSomeBlades( &ent->client->saber[0] ) )
2825 	{ //use staff stance then.
2826 		if ( ent->client->ps.saberHolstered == 1 )
2827 		{//second blade off
2828 			if ( ent->client->ps.saberInFlight )
2829 			{//can't turn second blade back on if it's in the air, you naughty boy!
2830 				if (d_saberStanceDebug.integer)
2831 				{
2832 					trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to toggle staff blade in air.\n\"") );
2833 				}
2834 				return;
2835 			}
2836 			//turn it on
2837 			G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn);
2838 			ent->client->ps.saberHolstered = 0;
2839 			//g_active should take care of this, but...
2840 			if ( ent->client->saber[0].stylesForbidden )
2841 			{//have a style we have to use
2842 				WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &selectLevel );
2843 				if ( ent->client->ps.weaponTime <= 0 )
2844 				{ //not busy, set it now
2845 					ent->client->ps.fd.saberAnimLevel = selectLevel;
2846 				}
2847 				else
2848 				{ //can't set it now or we might cause unexpected chaining, so queue it
2849 					ent->client->saberCycleQueue = selectLevel;
2850 				}
2851 			}
2852 		}
2853 		else if ( ent->client->ps.saberHolstered == 0 )
2854 		{//both blades on
2855 			if ( (ent->client->saber[0].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE) )
2856 			{//can't turn it off manually
2857 			}
2858 			else if ( ent->client->saber[0].bladeStyle2Start > 0
2859 				&& (ent->client->saber[0].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE2) )
2860 			{//can't turn it off manually
2861 			}
2862 			else
2863 			{
2864 				//turn second one off
2865 				G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOff);
2866 				ent->client->ps.saberHolstered = 1;
2867 				//g_active should take care of this, but...
2868 				if ( ent->client->saber[0].singleBladeStyle != SS_NONE )
2869 				{
2870 					if ( ent->client->ps.weaponTime <= 0 )
2871 					{ //not busy, set it now
2872 						ent->client->ps.fd.saberAnimLevel = ent->client->saber[0].singleBladeStyle;
2873 					}
2874 					else
2875 					{ //can't set it now or we might cause unexpected chaining, so queue it
2876 						ent->client->saberCycleQueue = ent->client->saber[0].singleBladeStyle;
2877 					}
2878 				}
2879 			}
2880 		}
2881 		if (d_saberStanceDebug.integer)
2882 		{
2883 			trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to toggle staff blade.\n\"") );
2884 		}
2885 		return;
2886 	}
2887 
2888 	if (ent->client->saberCycleQueue)
2889 	{ //resume off of the queue if we haven't gotten a chance to update it yet
2890 		selectLevel = ent->client->saberCycleQueue;
2891 	}
2892 	else
2893 	{
2894 		selectLevel = ent->client->ps.fd.saberAnimLevel;
2895 	}
2896 
2897 	if (level.gametype == GT_SIEGE &&
2898 		ent->client->siegeClass != -1 &&
2899 		bgSiegeClasses[ent->client->siegeClass].saberStance)
2900 	{ //we have a flag of useable stances so cycle through it instead
2901 		int i = selectLevel+1;
2902 
2903 		usingSiegeStyle = qtrue;
2904 
2905 		while (i != selectLevel)
2906 		{ //cycle around upward til we hit the next style or end up back on this one
2907 			if (i >= SS_NUM_SABER_STYLES)
2908 			{ //loop back around to the first valid
2909 				i = SS_FAST;
2910 			}
2911 
2912 			if (bgSiegeClasses[ent->client->siegeClass].saberStance & (1 << i))
2913 			{ //we can use this one, select it and break out.
2914 				selectLevel = i;
2915 				break;
2916 			}
2917 			i++;
2918 		}
2919 
2920 		if (d_saberStanceDebug.integer)
2921 		{
2922 			trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to cycle given class stance.\n\"") );
2923 		}
2924 	}
2925 	else
2926 	{
2927 		selectLevel++;
2928 		if ( selectLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] )
2929 		{
2930 			selectLevel = FORCE_LEVEL_1;
2931 		}
2932 		if (d_saberStanceDebug.integer)
2933 		{
2934 			trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to cycle stance normally.\n\"") );
2935 		}
2936 	}
2937 /*
2938 #ifndef FINAL_BUILD
2939 	switch ( selectLevel )
2940 	{
2941 	case FORCE_LEVEL_1:
2942 		trap->SendServerCommand( ent-g_entities, va("print \"Lightsaber Combat Style: %sfast\n\"", S_COLOR_BLUE) );
2943 		break;
2944 	case FORCE_LEVEL_2:
2945 		trap->SendServerCommand( ent-g_entities, va("print \"Lightsaber Combat Style: %smedium\n\"", S_COLOR_YELLOW) );
2946 		break;
2947 	case FORCE_LEVEL_3:
2948 		trap->SendServerCommand( ent-g_entities, va("print \"Lightsaber Combat Style: %sstrong\n\"", S_COLOR_RED) );
2949 		break;
2950 	}
2951 #endif
2952 */
2953 	if ( !usingSiegeStyle )
2954 	{
2955 		//make sure it's valid, change it if not
2956 		WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &selectLevel );
2957 	}
2958 
2959 	if (ent->client->ps.weaponTime <= 0)
2960 	{ //not busy, set it now
2961 		ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = selectLevel;
2962 	}
2963 	else
2964 	{ //can't set it now or we might cause unexpected chaining, so queue it
2965 		ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = selectLevel;
2966 	}
2967 }
2968 
G_OtherPlayersDueling(void)2969 qboolean G_OtherPlayersDueling(void)
2970 {
2971 	int i = 0;
2972 	gentity_t *ent;
2973 
2974 	while (i < MAX_CLIENTS)
2975 	{
2976 		ent = &g_entities[i];
2977 
2978 		if (ent && ent->inuse && ent->client && ent->client->ps.duelInProgress)
2979 		{
2980 			return qtrue;
2981 		}
2982 		i++;
2983 	}
2984 
2985 	return qfalse;
2986 }
2987 
Cmd_EngageDuel_f(gentity_t * ent)2988 void Cmd_EngageDuel_f(gentity_t *ent)
2989 {
2990 	trace_t tr;
2991 	vec3_t forward, fwdOrg;
2992 
2993 	if (!g_privateDuel.integer)
2994 	{
2995 		return;
2996 	}
2997 
2998 	if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
2999 	{ //rather pointless in this mode..
3000 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NODUEL_GAMETYPE")) );
3001 		return;
3002 	}
3003 
3004 	//if (level.gametype >= GT_TEAM && level.gametype != GT_SIEGE)
3005 	if (level.gametype >= GT_TEAM)
3006 	{ //no private dueling in team modes
3007 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NODUEL_GAMETYPE")) );
3008 		return;
3009 	}
3010 
3011 	if (ent->client->ps.duelTime >= level.time)
3012 	{
3013 		return;
3014 	}
3015 
3016 	if (ent->client->ps.weapon != WP_SABER)
3017 	{
3018 		return;
3019 	}
3020 
3021 	/*
3022 	if (!ent->client->ps.saberHolstered)
3023 	{ //must have saber holstered at the start of the duel
3024 		return;
3025 	}
3026 	*/
3027 	//NOTE: No longer doing this..
3028 
3029 	if (ent->client->ps.saberInFlight)
3030 	{
3031 		return;
3032 	}
3033 
3034 	if (ent->client->ps.duelInProgress)
3035 	{
3036 		return;
3037 	}
3038 
3039 	//New: Don't let a player duel if he just did and hasn't waited 10 seconds yet (note: If someone challenges him, his duel timer will reset so he can accept)
3040 	/*if (ent->client->ps.fd.privateDuelTime > level.time)
3041 	{
3042 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "CANTDUEL_JUSTDID")) );
3043 		return;
3044 	}
3045 
3046 	if (G_OtherPlayersDueling())
3047 	{
3048 		trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "CANTDUEL_BUSY")) );
3049 		return;
3050 	}*/
3051 
3052 	AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
3053 
3054 	fwdOrg[0] = ent->client->ps.origin[0] + forward[0]*256;
3055 	fwdOrg[1] = ent->client->ps.origin[1] + forward[1]*256;
3056 	fwdOrg[2] = (ent->client->ps.origin[2]+ent->client->ps.viewheight) + forward[2]*256;
3057 
3058 	trap->Trace(&tr, ent->client->ps.origin, NULL, NULL, fwdOrg, ent->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
3059 
3060 	if (tr.fraction != 1 && tr.entityNum < MAX_CLIENTS)
3061 	{
3062 		gentity_t *challenged = &g_entities[tr.entityNum];
3063 
3064 		if (!challenged || !challenged->client || !challenged->inuse ||
3065 			challenged->health < 1 || challenged->client->ps.stats[STAT_HEALTH] < 1 ||
3066 			challenged->client->ps.weapon != WP_SABER || challenged->client->ps.duelInProgress ||
3067 			challenged->client->ps.saberInFlight)
3068 		{
3069 			return;
3070 		}
3071 
3072 		if (level.gametype >= GT_TEAM && OnSameTeam(ent, challenged))
3073 		{
3074 			return;
3075 		}
3076 
3077 		if (challenged->client->ps.duelIndex == ent->s.number && challenged->client->ps.duelTime >= level.time)
3078 		{
3079 			trap->SendServerCommand( /*challenged-g_entities*/-1, va("print \"%s %s %s!\n\"", challenged->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELACCEPT"), ent->client->pers.netname) );
3080 
3081 			ent->client->ps.duelInProgress = qtrue;
3082 			challenged->client->ps.duelInProgress = qtrue;
3083 
3084 			ent->client->ps.duelTime = level.time + 2000;
3085 			challenged->client->ps.duelTime = level.time + 2000;
3086 
3087 			G_AddEvent(ent, EV_PRIVATE_DUEL, 1);
3088 			G_AddEvent(challenged, EV_PRIVATE_DUEL, 1);
3089 
3090 			//Holster their sabers now, until the duel starts (then they'll get auto-turned on to look cool)
3091 
3092 			if (!ent->client->ps.saberHolstered)
3093 			{
3094 				if (ent->client->saber[0].soundOff)
3095 				{
3096 					G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOff);
3097 				}
3098 				if (ent->client->saber[1].soundOff &&
3099 					ent->client->saber[1].model[0])
3100 				{
3101 					G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff);
3102 				}
3103 				ent->client->ps.weaponTime = 400;
3104 				ent->client->ps.saberHolstered = 2;
3105 			}
3106 			if (!challenged->client->ps.saberHolstered)
3107 			{
3108 				if (challenged->client->saber[0].soundOff)
3109 				{
3110 					G_Sound(challenged, CHAN_AUTO, challenged->client->saber[0].soundOff);
3111 				}
3112 				if (challenged->client->saber[1].soundOff &&
3113 					challenged->client->saber[1].model[0])
3114 				{
3115 					G_Sound(challenged, CHAN_AUTO, challenged->client->saber[1].soundOff);
3116 				}
3117 				challenged->client->ps.weaponTime = 400;
3118 				challenged->client->ps.saberHolstered = 2;
3119 			}
3120 		}
3121 		else
3122 		{
3123 			//Print the message that a player has been challenged in private, only announce the actual duel initiation in private
3124 			trap->SendServerCommand( challenged-g_entities, va("cp \"%s %s\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELCHALLENGE")) );
3125 			trap->SendServerCommand( ent-g_entities, va("cp \"%s %s\n\"", G_GetStringEdString("MP_SVGAME", "PLDUELCHALLENGED"), challenged->client->pers.netname) );
3126 		}
3127 
3128 		challenged->client->ps.fd.privateDuelTime = 0; //reset the timer in case this player just got out of a duel. He should still be able to accept the challenge.
3129 
3130 		ent->client->ps.forceHandExtend = HANDEXTEND_DUELCHALLENGE;
3131 		ent->client->ps.forceHandExtendTime = level.time + 1000;
3132 
3133 		ent->client->ps.duelIndex = challenged->s.number;
3134 		ent->client->ps.duelTime = level.time + 5000;
3135 	}
3136 }
3137 
3138 #ifndef FINAL_BUILD
3139 extern stringID_table_t animTable[MAX_ANIMATIONS+1];
3140 
Cmd_DebugSetSaberMove_f(gentity_t * self)3141 void Cmd_DebugSetSaberMove_f(gentity_t *self)
3142 {
3143 	int argNum = trap->Argc();
3144 	char arg[MAX_STRING_CHARS];
3145 
3146 	if (argNum < 2)
3147 	{
3148 		return;
3149 	}
3150 
3151 	trap->Argv( 1, arg, sizeof( arg ) );
3152 
3153 	if (!arg[0])
3154 	{
3155 		return;
3156 	}
3157 
3158 	self->client->ps.saberMove = atoi(arg);
3159 	self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
3160 
3161 	if (self->client->ps.saberMove >= LS_MOVE_MAX)
3162 	{
3163 		self->client->ps.saberMove = LS_MOVE_MAX-1;
3164 	}
3165 
3166 	Com_Printf("Anim for move: %s\n", animTable[saberMoveData[self->client->ps.saberMove].animToUse].name);
3167 }
3168 
Cmd_DebugSetBodyAnim_f(gentity_t * self)3169 void Cmd_DebugSetBodyAnim_f(gentity_t *self)
3170 {
3171 	int argNum = trap->Argc();
3172 	char arg[MAX_STRING_CHARS];
3173 	int i = 0;
3174 
3175 	if (argNum < 2)
3176 	{
3177 		return;
3178 	}
3179 
3180 	trap->Argv( 1, arg, sizeof( arg ) );
3181 
3182 	if (!arg[0])
3183 	{
3184 		return;
3185 	}
3186 
3187 	while (i < MAX_ANIMATIONS)
3188 	{
3189 		if (!Q_stricmp(arg, animTable[i].name))
3190 		{
3191 			break;
3192 		}
3193 		i++;
3194 	}
3195 
3196 	if (i == MAX_ANIMATIONS)
3197 	{
3198 		Com_Printf("Animation '%s' does not exist\n", arg);
3199 		return;
3200 	}
3201 
3202 	G_SetAnim(self, NULL, SETANIM_BOTH, i, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
3203 
3204 	Com_Printf("Set body anim to %s\n", arg);
3205 }
3206 #endif
3207 
StandardSetBodyAnim(gentity_t * self,int anim,int flags)3208 void StandardSetBodyAnim(gentity_t *self, int anim, int flags)
3209 {
3210 	G_SetAnim(self, NULL, SETANIM_BOTH, anim, flags, 0);
3211 }
3212 
3213 void DismembermentTest(gentity_t *self);
3214 
3215 void Bot_SetForcedMovement(int bot, int forward, int right, int up);
3216 
3217 #ifndef FINAL_BUILD
3218 extern void DismembermentByNum(gentity_t *self, int num);
3219 extern void G_SetVehDamageFlags( gentity_t *veh, int shipSurf, int damageLevel );
3220 #endif
3221 
TryGrapple(gentity_t * ent)3222 qboolean TryGrapple(gentity_t *ent)
3223 {
3224 	if (ent->client->ps.weaponTime > 0)
3225 	{ //weapon busy
3226 		return qfalse;
3227 	}
3228 	if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
3229 	{ //force power or knockdown or something
3230 		return qfalse;
3231 	}
3232 	if (ent->client->grappleState)
3233 	{ //already grappling? but weapontime should be > 0 then..
3234 		return qfalse;
3235 	}
3236 
3237 	if (ent->client->ps.weapon != WP_SABER && ent->client->ps.weapon != WP_MELEE)
3238 	{
3239 		return qfalse;
3240 	}
3241 
3242 	if (ent->client->ps.weapon == WP_SABER && !ent->client->ps.saberHolstered)
3243 	{
3244 		Cmd_ToggleSaber_f(ent);
3245 		if (!ent->client->ps.saberHolstered)
3246 		{ //must have saber holstered
3247 			return qfalse;
3248 		}
3249 	}
3250 
3251 	//G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_PA_1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
3252 	G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
3253 	if (ent->client->ps.torsoAnim == BOTH_KYLE_GRAB)
3254 	{ //providing the anim set succeeded..
3255 		ent->client->ps.torsoTimer += 500; //make the hand stick out a little longer than it normally would
3256 		if (ent->client->ps.legsAnim == ent->client->ps.torsoAnim)
3257 		{
3258 			ent->client->ps.legsTimer = ent->client->ps.torsoTimer;
3259 		}
3260 		ent->client->ps.weaponTime = ent->client->ps.torsoTimer;
3261 		ent->client->dangerTime = level.time;
3262 		return qtrue;
3263 	}
3264 
3265 	return qfalse;
3266 }
3267 
Cmd_TargetUse_f(gentity_t * ent)3268 void Cmd_TargetUse_f( gentity_t *ent )
3269 {
3270 	if ( trap->Argc() > 1 )
3271 	{
3272 		char sArg[MAX_STRING_CHARS] = {0};
3273 		gentity_t *targ;
3274 
3275 		trap->Argv( 1, sArg, sizeof( sArg ) );
3276 		targ = G_Find( NULL, FOFS( targetname ), sArg );
3277 
3278 		while ( targ )
3279 		{
3280 			if ( targ->use )
3281 				targ->use( targ, ent, ent );
3282 			targ = G_Find( targ, FOFS( targetname ), sArg );
3283 		}
3284 	}
3285 }
3286 
Cmd_TheDestroyer_f(gentity_t * ent)3287 void Cmd_TheDestroyer_f( gentity_t *ent ) {
3288 	if ( !ent->client->ps.saberHolstered || ent->client->ps.weapon != WP_SABER )
3289 		return;
3290 
3291 	Cmd_ToggleSaber_f( ent );
3292 }
3293 
Cmd_BotMoveForward_f(gentity_t * ent)3294 void Cmd_BotMoveForward_f( gentity_t *ent ) {
3295 	int arg = 4000;
3296 	int bCl = 0;
3297 	char sarg[MAX_STRING_CHARS];
3298 
3299 	assert( trap->Argc() > 1 );
3300 	trap->Argv( 1, sarg, sizeof( sarg ) );
3301 
3302 	assert( sarg[0] );
3303 	bCl = atoi( sarg );
3304 	Bot_SetForcedMovement( bCl, arg, -1, -1 );
3305 }
3306 
Cmd_BotMoveBack_f(gentity_t * ent)3307 void Cmd_BotMoveBack_f( gentity_t *ent ) {
3308 	int arg = -4000;
3309 	int bCl = 0;
3310 	char sarg[MAX_STRING_CHARS];
3311 
3312 	assert( trap->Argc() > 1 );
3313 	trap->Argv( 1, sarg, sizeof( sarg ) );
3314 
3315 	assert( sarg[0] );
3316 	bCl = atoi( sarg );
3317 	Bot_SetForcedMovement( bCl, arg, -1, -1 );
3318 }
3319 
Cmd_BotMoveRight_f(gentity_t * ent)3320 void Cmd_BotMoveRight_f( gentity_t *ent ) {
3321 	int arg = 4000;
3322 	int bCl = 0;
3323 	char sarg[MAX_STRING_CHARS];
3324 
3325 	assert( trap->Argc() > 1 );
3326 	trap->Argv( 1, sarg, sizeof( sarg ) );
3327 
3328 	assert( sarg[0] );
3329 	bCl = atoi( sarg );
3330 	Bot_SetForcedMovement( bCl, -1, arg, -1 );
3331 }
3332 
Cmd_BotMoveLeft_f(gentity_t * ent)3333 void Cmd_BotMoveLeft_f( gentity_t *ent ) {
3334 	int arg = -4000;
3335 	int bCl = 0;
3336 	char sarg[MAX_STRING_CHARS];
3337 
3338 	assert( trap->Argc() > 1 );
3339 	trap->Argv( 1, sarg, sizeof( sarg ) );
3340 
3341 	assert( sarg[0] );
3342 	bCl = atoi( sarg );
3343 	Bot_SetForcedMovement( bCl, -1, arg, -1 );
3344 }
3345 
Cmd_BotMoveUp_f(gentity_t * ent)3346 void Cmd_BotMoveUp_f( gentity_t *ent ) {
3347 	int arg = 4000;
3348 	int bCl = 0;
3349 	char sarg[MAX_STRING_CHARS];
3350 
3351 	assert( trap->Argc() > 1 );
3352 	trap->Argv( 1, sarg, sizeof( sarg ) );
3353 
3354 	assert( sarg[0] );
3355 	bCl = atoi( sarg );
3356 	Bot_SetForcedMovement( bCl, -1, -1, arg );
3357 }
3358 
Cmd_AddBot_f(gentity_t * ent)3359 void Cmd_AddBot_f( gentity_t *ent ) {
3360 	//because addbot isn't a recognized command unless you're the server, but it is in the menus regardless
3361 	trap->SendServerCommand( ent-g_entities, va( "print \"%s.\n\"", G_GetStringEdString( "MP_SVGAME", "ONLY_ADD_BOTS_AS_SERVER" ) ) );
3362 }
3363 
3364 /*
3365 =================
3366 ClientCommand
3367 =================
3368 */
3369 
3370 #define CMD_NOINTERMISSION		(1<<0)
3371 #define CMD_CHEAT				(1<<1)
3372 #define CMD_ALIVE				(1<<2)
3373 
3374 typedef struct command_s {
3375 	const char	*name;
3376 	void		(*func)(gentity_t *ent);
3377 	int			flags;
3378 } command_t;
3379 
cmdcmp(const void * a,const void * b)3380 int cmdcmp( const void *a, const void *b ) {
3381 	return Q_stricmp( (const char *)a, ((command_t*)b)->name );
3382 }
3383 
3384 command_t commands[] = {
3385 	{ "addbot",				Cmd_AddBot_f,				0 },
3386 	{ "callteamvote",		Cmd_CallTeamVote_f,			CMD_NOINTERMISSION },
3387 	{ "callvote",			Cmd_CallVote_f,				CMD_NOINTERMISSION },
3388 	{ "debugBMove_Back",	Cmd_BotMoveBack_f,			CMD_CHEAT|CMD_ALIVE },
3389 	{ "debugBMove_Forward",	Cmd_BotMoveForward_f,		CMD_CHEAT|CMD_ALIVE },
3390 	{ "debugBMove_Left",	Cmd_BotMoveLeft_f,			CMD_CHEAT|CMD_ALIVE },
3391 	{ "debugBMove_Right",	Cmd_BotMoveRight_f,			CMD_CHEAT|CMD_ALIVE },
3392 	{ "debugBMove_Up",		Cmd_BotMoveUp_f,			CMD_CHEAT|CMD_ALIVE },
3393 	{ "duelteam",			Cmd_DuelTeam_f,				CMD_NOINTERMISSION },
3394 	{ "follow",				Cmd_Follow_f,				CMD_NOINTERMISSION },
3395 	{ "follownext",			Cmd_FollowNext_f,			CMD_NOINTERMISSION },
3396 	{ "followprev",			Cmd_FollowPrev_f,			CMD_NOINTERMISSION },
3397 	{ "forcechanged",		Cmd_ForceChanged_f,			0 },
3398 	{ "gc",					Cmd_GameCommand_f,			CMD_NOINTERMISSION },
3399 	{ "give",				Cmd_Give_f,					CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION },
3400 	{ "giveother",			Cmd_GiveOther_f,			CMD_CHEAT|CMD_NOINTERMISSION },
3401 	{ "god",				Cmd_God_f,					CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION },
3402 	{ "kill",				Cmd_Kill_f,					CMD_ALIVE|CMD_NOINTERMISSION },
3403 	{ "killother",			Cmd_KillOther_f,			CMD_CHEAT|CMD_NOINTERMISSION },
3404 //	{ "kylesmash",			TryGrapple,					0 },
3405 	{ "levelshot",			Cmd_LevelShot_f,			CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION },
3406 	{ "maplist",			Cmd_MapList_f,				CMD_NOINTERMISSION },
3407 	{ "noclip",				Cmd_Noclip_f,				CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION },
3408 	{ "notarget",			Cmd_Notarget_f,				CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION },
3409 	{ "npc",				Cmd_NPC_f,					CMD_CHEAT|CMD_ALIVE },
3410 	{ "say",				Cmd_Say_f,					0 },
3411 	{ "say_team",			Cmd_SayTeam_f,				0 },
3412 	{ "score",				Cmd_Score_f,				0 },
3413 	{ "setviewpos",			Cmd_SetViewpos_f,			CMD_CHEAT|CMD_NOINTERMISSION },
3414 	{ "siegeclass",			Cmd_SiegeClass_f,			CMD_NOINTERMISSION },
3415 	{ "team",				Cmd_Team_f,					CMD_NOINTERMISSION },
3416 //	{ "teamtask",			Cmd_TeamTask_f,				CMD_NOINTERMISSION },
3417 	{ "teamvote",			Cmd_TeamVote_f,				CMD_NOINTERMISSION },
3418 	{ "tell",				Cmd_Tell_f,					0 },
3419 	{ "thedestroyer",		Cmd_TheDestroyer_f,			CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION },
3420 	{ "t_use",				Cmd_TargetUse_f,			CMD_CHEAT|CMD_ALIVE },
3421 	{ "voice_cmd",			Cmd_VoiceCommand_f,			CMD_NOINTERMISSION },
3422 	{ "vote",				Cmd_Vote_f,					CMD_NOINTERMISSION },
3423 	{ "where",				Cmd_Where_f,				CMD_NOINTERMISSION },
3424 };
3425 static const size_t numCommands = ARRAY_LEN( commands );
3426 
ClientCommand(int clientNum)3427 void ClientCommand( int clientNum ) {
3428 	gentity_t	*ent = NULL;
3429 	char		cmd[MAX_TOKEN_CHARS] = {0};
3430 	command_t	*command = NULL;
3431 
3432 	ent = g_entities + clientNum;
3433 	if ( !ent->client || ent->client->pers.connected != CON_CONNECTED ) {
3434 		G_SecurityLogPrintf( "ClientCommand(%d) without an active connection\n", clientNum );
3435 		return;		// not fully in game yet
3436 	}
3437 
3438 	trap->Argv( 0, cmd, sizeof( cmd ) );
3439 
3440 	//rww - redirect bot commands
3441 	if ( strstr( cmd, "bot_" ) && AcceptBotCommand( cmd, ent ) )
3442 		return;
3443 	//end rww
3444 
3445 	command = (command_t *)Q_LinearSearch( cmd, commands, numCommands, sizeof( commands[0] ), cmdcmp );
3446 	if ( !command )
3447 	{
3448 		trap->SendServerCommand( clientNum, va( "print \"Unknown command %s\n\"", cmd ) );
3449 		return;
3450 	}
3451 
3452 	else if ( (command->flags & CMD_NOINTERMISSION)
3453 		&& ( level.intermissionQueued || level.intermissiontime ) )
3454 	{
3455 		trap->SendServerCommand( clientNum, va( "print \"%s (%s)\n\"", G_GetStringEdString( "MP_SVGAME", "CANNOT_TASK_INTERMISSION" ), cmd ) );
3456 		return;
3457 	}
3458 
3459 	else if ( (command->flags & CMD_CHEAT)
3460 		&& !sv_cheats.integer )
3461 	{
3462 		trap->SendServerCommand( clientNum, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOCHEATS" ) ) );
3463 		return;
3464 	}
3465 
3466 	else if ( (command->flags & CMD_ALIVE)
3467 		&& (ent->health <= 0
3468 			|| ent->client->tempSpectate >= level.time
3469 			|| ent->client->sess.sessionTeam == TEAM_SPECTATOR) )
3470 	{
3471 		trap->SendServerCommand( clientNum, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) );
3472 		return;
3473 	}
3474 
3475 	else
3476 		command->func( ent );
3477 }
3478