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