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