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