g_client.c (33822B)
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 // g_client.c -- client functions that don't happen every frame 26 27 static vec3_t playerMins = {-15, -15, -24}; 28 static vec3_t playerMaxs = {15, 15, 32}; 29 30 /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial 31 potential spawning position for deathmatch games. 32 The first time a player enters the game, they will be at an 'initial' spot. 33 Targets will be fired when someone spawns in on them. 34 "nobots" will prevent bots from using this spot. 35 "nohumans" will prevent non-bots from using this spot. 36 */ 37 void SP_info_player_deathmatch( gentity_t *ent ) { 38 int i; 39 40 G_SpawnInt( "nobots", "0", &i); 41 if ( i ) { 42 ent->flags |= FL_NO_BOTS; 43 } 44 G_SpawnInt( "nohumans", "0", &i ); 45 if ( i ) { 46 ent->flags |= FL_NO_HUMANS; 47 } 48 } 49 50 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) 51 equivelant to info_player_deathmatch 52 */ 53 void SP_info_player_start(gentity_t *ent) { 54 ent->classname = "info_player_deathmatch"; 55 SP_info_player_deathmatch( ent ); 56 } 57 58 /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) 59 The intermission will be viewed from this point. Target an info_notnull for the view direction. 60 */ 61 void SP_info_player_intermission( gentity_t *ent ) { 62 63 } 64 65 66 67 /* 68 ======================================================================= 69 70 SelectSpawnPoint 71 72 ======================================================================= 73 */ 74 75 /* 76 ================ 77 SpotWouldTelefrag 78 79 ================ 80 */ 81 qboolean SpotWouldTelefrag( gentity_t *spot ) { 82 int i, num; 83 int touch[MAX_GENTITIES]; 84 gentity_t *hit; 85 vec3_t mins, maxs; 86 87 VectorAdd( spot->s.origin, playerMins, mins ); 88 VectorAdd( spot->s.origin, playerMaxs, maxs ); 89 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); 90 91 for (i=0 ; i<num ; i++) { 92 hit = &g_entities[touch[i]]; 93 //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) { 94 if ( hit->client) { 95 return qtrue; 96 } 97 98 } 99 100 return qfalse; 101 } 102 103 /* 104 ================ 105 SelectNearestDeathmatchSpawnPoint 106 107 Find the spot that we DON'T want to use 108 ================ 109 */ 110 #define MAX_SPAWN_POINTS 128 111 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { 112 gentity_t *spot; 113 vec3_t delta; 114 float dist, nearestDist; 115 gentity_t *nearestSpot; 116 117 nearestDist = 999999; 118 nearestSpot = NULL; 119 spot = NULL; 120 121 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { 122 123 VectorSubtract( spot->s.origin, from, delta ); 124 dist = VectorLength( delta ); 125 if ( dist < nearestDist ) { 126 nearestDist = dist; 127 nearestSpot = spot; 128 } 129 } 130 131 return nearestSpot; 132 } 133 134 135 /* 136 ================ 137 SelectRandomDeathmatchSpawnPoint 138 139 go to a random point that doesn't telefrag 140 ================ 141 */ 142 #define MAX_SPAWN_POINTS 128 143 gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { 144 gentity_t *spot; 145 int count; 146 int selection; 147 gentity_t *spots[MAX_SPAWN_POINTS]; 148 149 count = 0; 150 spot = NULL; 151 152 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { 153 if ( SpotWouldTelefrag( spot ) ) { 154 continue; 155 } 156 spots[ count ] = spot; 157 count++; 158 } 159 160 if ( !count ) { // no spots that won't telefrag 161 return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); 162 } 163 164 selection = rand() % count; 165 return spots[ selection ]; 166 } 167 168 /* 169 =========== 170 SelectRandomFurthestSpawnPoint 171 172 Chooses a player start, deathmatch start, etc 173 ============ 174 */ 175 gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { 176 gentity_t *spot; 177 vec3_t delta; 178 float dist; 179 float list_dist[64]; 180 gentity_t *list_spot[64]; 181 int numSpots, rnd, i, j; 182 183 numSpots = 0; 184 spot = NULL; 185 186 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { 187 if ( SpotWouldTelefrag( spot ) ) { 188 continue; 189 } 190 VectorSubtract( spot->s.origin, avoidPoint, delta ); 191 dist = VectorLength( delta ); 192 for (i = 0; i < numSpots; i++) { 193 if ( dist > list_dist[i] ) { 194 if ( numSpots >= 64 ) 195 numSpots = 64-1; 196 for (j = numSpots; j > i; j--) { 197 list_dist[j] = list_dist[j-1]; 198 list_spot[j] = list_spot[j-1]; 199 } 200 list_dist[i] = dist; 201 list_spot[i] = spot; 202 numSpots++; 203 if (numSpots > 64) 204 numSpots = 64; 205 break; 206 } 207 } 208 if (i >= numSpots && numSpots < 64) { 209 list_dist[numSpots] = dist; 210 list_spot[numSpots] = spot; 211 numSpots++; 212 } 213 } 214 if (!numSpots) { 215 spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); 216 if (!spot) 217 G_Error( "Couldn't find a spawn point" ); 218 VectorCopy (spot->s.origin, origin); 219 origin[2] += 9; 220 VectorCopy (spot->s.angles, angles); 221 return spot; 222 } 223 224 // select a random spot from the spawn points furthest away 225 rnd = random() * (numSpots / 2); 226 227 VectorCopy (list_spot[rnd]->s.origin, origin); 228 origin[2] += 9; 229 VectorCopy (list_spot[rnd]->s.angles, angles); 230 231 return list_spot[rnd]; 232 } 233 234 /* 235 =========== 236 SelectSpawnPoint 237 238 Chooses a player start, deathmatch start, etc 239 ============ 240 */ 241 gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { 242 return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); 243 244 /* 245 gentity_t *spot; 246 gentity_t *nearestSpot; 247 248 nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); 249 250 spot = SelectRandomDeathmatchSpawnPoint ( ); 251 if ( spot == nearestSpot ) { 252 // roll again if it would be real close to point of death 253 spot = SelectRandomDeathmatchSpawnPoint ( ); 254 if ( spot == nearestSpot ) { 255 // last try 256 spot = SelectRandomDeathmatchSpawnPoint ( ); 257 } 258 } 259 260 // find a single player start spot 261 if (!spot) { 262 G_Error( "Couldn't find a spawn point" ); 263 } 264 265 VectorCopy (spot->s.origin, origin); 266 origin[2] += 9; 267 VectorCopy (spot->s.angles, angles); 268 269 return spot; 270 */ 271 } 272 273 /* 274 =========== 275 SelectInitialSpawnPoint 276 277 Try to find a spawn point marked 'initial', otherwise 278 use normal spawn selection. 279 ============ 280 */ 281 gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { 282 gentity_t *spot; 283 284 spot = NULL; 285 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { 286 if ( spot->spawnflags & 1 ) { 287 break; 288 } 289 } 290 291 if ( !spot || SpotWouldTelefrag( spot ) ) { 292 return SelectSpawnPoint( vec3_origin, origin, angles ); 293 } 294 295 VectorCopy (spot->s.origin, origin); 296 origin[2] += 9; 297 VectorCopy (spot->s.angles, angles); 298 299 return spot; 300 } 301 302 /* 303 =========== 304 SelectSpectatorSpawnPoint 305 306 ============ 307 */ 308 gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { 309 FindIntermissionPoint(); 310 311 VectorCopy( level.intermission_origin, origin ); 312 VectorCopy( level.intermission_angle, angles ); 313 314 return NULL; 315 } 316 317 /* 318 ======================================================================= 319 320 BODYQUE 321 322 ======================================================================= 323 */ 324 325 /* 326 =============== 327 InitBodyQue 328 =============== 329 */ 330 void InitBodyQue (void) { 331 int i; 332 gentity_t *ent; 333 334 level.bodyQueIndex = 0; 335 for (i=0; i<BODY_QUEUE_SIZE ; i++) { 336 ent = G_Spawn(); 337 ent->classname = "bodyque"; 338 ent->neverFree = qtrue; 339 level.bodyQue[i] = ent; 340 } 341 } 342 343 /* 344 ============= 345 BodySink 346 347 After sitting around for five seconds, fall into the ground and dissapear 348 ============= 349 */ 350 void BodySink( gentity_t *ent ) { 351 if ( level.time - ent->timestamp > 6500 ) { 352 // the body ques are never actually freed, they are just unlinked 353 trap_UnlinkEntity( ent ); 354 ent->physicsObject = qfalse; 355 return; 356 } 357 ent->nextthink = level.time + 100; 358 ent->s.pos.trBase[2] -= 1; 359 } 360 361 /* 362 ============= 363 CopyToBodyQue 364 365 A player is respawning, so make an entity that looks 366 just like the existing corpse to leave behind. 367 ============= 368 */ 369 void CopyToBodyQue( gentity_t *ent ) { 370 #ifdef MISSIONPACK 371 gentity_t *e; 372 int i; 373 #endif 374 gentity_t *body; 375 int contents; 376 377 trap_UnlinkEntity (ent); 378 379 // if client is in a nodrop area, don't leave the body 380 contents = trap_PointContents( ent->s.origin, -1 ); 381 if ( contents & CONTENTS_NODROP ) { 382 return; 383 } 384 385 // grab a body que and cycle to the next one 386 body = level.bodyQue[ level.bodyQueIndex ]; 387 level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; 388 389 trap_UnlinkEntity (body); 390 391 body->s = ent->s; 392 body->s.eFlags = EF_DEAD; // clear EF_TALK, etc 393 #ifdef MISSIONPACK 394 if ( ent->s.eFlags & EF_KAMIKAZE ) { 395 body->s.eFlags |= EF_KAMIKAZE; 396 397 // check if there is a kamikaze timer around for this owner 398 for (i = 0; i < MAX_GENTITIES; i++) { 399 e = &g_entities[i]; 400 if (!e->inuse) 401 continue; 402 if (e->activator != ent) 403 continue; 404 if (strcmp(e->classname, "kamikaze timer")) 405 continue; 406 e->activator = body; 407 break; 408 } 409 } 410 #endif 411 body->s.powerups = 0; // clear powerups 412 body->s.loopSound = 0; // clear lava burning 413 body->s.number = body - g_entities; 414 body->timestamp = level.time; 415 body->physicsObject = qtrue; 416 body->physicsBounce = 0; // don't bounce 417 if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { 418 body->s.pos.trType = TR_GRAVITY; 419 body->s.pos.trTime = level.time; 420 VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); 421 } else { 422 body->s.pos.trType = TR_STATIONARY; 423 } 424 body->s.event = 0; 425 426 // change the animation to the last-frame only, so the sequence 427 // doesn't repeat anew for the body 428 switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { 429 case BOTH_DEATH1: 430 case BOTH_DEAD1: 431 body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; 432 break; 433 case BOTH_DEATH2: 434 case BOTH_DEAD2: 435 body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; 436 break; 437 case BOTH_DEATH3: 438 case BOTH_DEAD3: 439 default: 440 body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; 441 break; 442 } 443 444 body->r.svFlags = ent->r.svFlags; 445 VectorCopy (ent->r.mins, body->r.mins); 446 VectorCopy (ent->r.maxs, body->r.maxs); 447 VectorCopy (ent->r.absmin, body->r.absmin); 448 VectorCopy (ent->r.absmax, body->r.absmax); 449 450 body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; 451 body->r.contents = CONTENTS_CORPSE; 452 body->r.ownerNum = ent->s.number; 453 454 body->nextthink = level.time + 5000; 455 body->think = BodySink; 456 457 body->die = body_die; 458 459 // don't take more damage if already gibbed 460 if ( ent->health <= GIB_HEALTH ) { 461 body->takedamage = qfalse; 462 } else { 463 body->takedamage = qtrue; 464 } 465 466 467 VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); 468 trap_LinkEntity (body); 469 } 470 471 //====================================================================== 472 473 474 /* 475 ================== 476 SetClientViewAngle 477 478 ================== 479 */ 480 void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { 481 int i; 482 483 // set the delta angle 484 for (i=0 ; i<3 ; i++) { 485 int cmdAngle; 486 487 cmdAngle = ANGLE2SHORT(angle[i]); 488 ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; 489 } 490 VectorCopy( angle, ent->s.angles ); 491 VectorCopy (ent->s.angles, ent->client->ps.viewangles); 492 } 493 494 /* 495 ================ 496 respawn 497 ================ 498 */ 499 void respawn( gentity_t *ent ) { 500 gentity_t *tent; 501 502 CopyToBodyQue (ent); 503 ClientSpawn(ent); 504 505 // add a teleportation effect 506 tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); 507 tent->s.clientNum = ent->s.clientNum; 508 } 509 510 /* 511 ================ 512 TeamCount 513 514 Returns number of players on a team 515 ================ 516 */ 517 team_t TeamCount( int ignoreClientNum, int team ) { 518 int i; 519 int count = 0; 520 521 for ( i = 0 ; i < level.maxclients ; i++ ) { 522 if ( i == ignoreClientNum ) { 523 continue; 524 } 525 if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { 526 continue; 527 } 528 if ( level.clients[i].sess.sessionTeam == team ) { 529 count++; 530 } 531 } 532 533 return count; 534 } 535 536 /* 537 ================ 538 TeamLeader 539 540 Returns the client number of the team leader 541 ================ 542 */ 543 int TeamLeader( int team ) { 544 int i; 545 546 for ( i = 0 ; i < level.maxclients ; i++ ) { 547 if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { 548 continue; 549 } 550 if ( level.clients[i].sess.sessionTeam == team ) { 551 if ( level.clients[i].sess.teamLeader ) 552 return i; 553 } 554 } 555 556 return -1; 557 } 558 559 560 /* 561 ================ 562 PickTeam 563 564 ================ 565 */ 566 team_t PickTeam( int ignoreClientNum ) { 567 int counts[TEAM_NUM_TEAMS]; 568 569 counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); 570 counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); 571 572 if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { 573 return TEAM_RED; 574 } 575 if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { 576 return TEAM_BLUE; 577 } 578 // equal team count, so join the team with the lowest score 579 if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { 580 return TEAM_RED; 581 } 582 return TEAM_BLUE; 583 } 584 585 /* 586 =========== 587 ForceClientSkin 588 589 Forces a client's skin (for teamplay) 590 =========== 591 */ 592 /* 593 static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { 594 char *p; 595 596 if ((p = Q_strrchr(model, '/')) != 0) { 597 *p = 0; 598 } 599 600 Q_strcat(model, MAX_QPATH, "/"); 601 Q_strcat(model, MAX_QPATH, skin); 602 } 603 */ 604 605 /* 606 =========== 607 ClientCheckName 608 ============ 609 */ 610 static void ClientCleanName( const char *in, char *out, int outSize ) { 611 int len, colorlessLen; 612 char ch; 613 char *p; 614 int spaces; 615 616 //save room for trailing null byte 617 outSize--; 618 619 len = 0; 620 colorlessLen = 0; 621 p = out; 622 *p = 0; 623 spaces = 0; 624 625 while( 1 ) { 626 ch = *in++; 627 if( !ch ) { 628 break; 629 } 630 631 // don't allow leading spaces 632 if( !*p && ch == ' ' ) { 633 continue; 634 } 635 636 // check colors 637 if( ch == Q_COLOR_ESCAPE ) { 638 // solo trailing carat is not a color prefix 639 if( !*in ) { 640 break; 641 } 642 643 // don't allow black in a name, period 644 if( ColorIndex(*in) == 0 ) { 645 in++; 646 continue; 647 } 648 649 // make sure room in dest for both chars 650 if( len > outSize - 2 ) { 651 break; 652 } 653 654 *out++ = ch; 655 *out++ = *in++; 656 len += 2; 657 continue; 658 } 659 660 // don't allow too many consecutive spaces 661 if( ch == ' ' ) { 662 spaces++; 663 if( spaces > 3 ) { 664 continue; 665 } 666 } 667 else { 668 spaces = 0; 669 } 670 671 if( len > outSize - 1 ) { 672 break; 673 } 674 675 *out++ = ch; 676 colorlessLen++; 677 len++; 678 } 679 *out = 0; 680 681 // don't allow empty names 682 if( *p == 0 || colorlessLen == 0 ) { 683 Q_strncpyz( p, "UnnamedPlayer", outSize ); 684 } 685 } 686 687 688 /* 689 =========== 690 ClientUserInfoChanged 691 692 Called from ClientConnect when the player first connects and 693 directly by the server system when the player updates a userinfo variable. 694 695 The game can override any of the settings and call trap_SetUserinfo 696 if desired. 697 ============ 698 */ 699 void ClientUserinfoChanged( int clientNum ) { 700 gentity_t *ent; 701 int teamTask, teamLeader, team, health; 702 char *s; 703 char model[MAX_QPATH]; 704 char headModel[MAX_QPATH]; 705 char oldname[MAX_STRING_CHARS]; 706 gclient_t *client; 707 char c1[MAX_INFO_STRING]; 708 char c2[MAX_INFO_STRING]; 709 char redTeam[MAX_INFO_STRING]; 710 char blueTeam[MAX_INFO_STRING]; 711 char userinfo[MAX_INFO_STRING]; 712 713 ent = g_entities + clientNum; 714 client = ent->client; 715 716 trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); 717 718 // check for malformed or illegal info strings 719 if ( !Info_Validate(userinfo) ) { 720 strcpy (userinfo, "\\name\\badinfo"); 721 } 722 723 // check for local client 724 s = Info_ValueForKey( userinfo, "ip" ); 725 if ( !strcmp( s, "localhost" ) ) { 726 client->pers.localClient = qtrue; 727 } 728 729 // check the item prediction 730 s = Info_ValueForKey( userinfo, "cg_predictItems" ); 731 if ( !atoi( s ) ) { 732 client->pers.predictItemPickup = qfalse; 733 } else { 734 client->pers.predictItemPickup = qtrue; 735 } 736 737 // set name 738 Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); 739 s = Info_ValueForKey (userinfo, "name"); 740 ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); 741 742 if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { 743 if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { 744 Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); 745 } 746 } 747 748 if ( client->pers.connected == CON_CONNECTED ) { 749 if ( strcmp( oldname, client->pers.netname ) ) { 750 trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, 751 client->pers.netname) ); 752 } 753 } 754 755 // set max health 756 #ifdef MISSIONPACK 757 if (client->ps.powerups[PW_GUARD]) { 758 client->pers.maxHealth = 200; 759 } else { 760 health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); 761 client->pers.maxHealth = health; 762 if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { 763 client->pers.maxHealth = 100; 764 } 765 } 766 #else 767 health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); 768 client->pers.maxHealth = health; 769 if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { 770 client->pers.maxHealth = 100; 771 } 772 #endif 773 client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; 774 775 // set model 776 if( g_gametype.integer >= GT_TEAM ) { 777 Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); 778 Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); 779 } else { 780 Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); 781 Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); 782 } 783 784 // bots set their team a few frames later 785 if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { 786 s = Info_ValueForKey( userinfo, "team" ); 787 if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { 788 team = TEAM_RED; 789 } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { 790 team = TEAM_BLUE; 791 } else { 792 // pick the team with the least number of players 793 team = PickTeam( clientNum ); 794 } 795 } 796 else { 797 team = client->sess.sessionTeam; 798 } 799 800 /* NOTE: all client side now 801 802 // team 803 switch( team ) { 804 case TEAM_RED: 805 ForceClientSkin(client, model, "red"); 806 // ForceClientSkin(client, headModel, "red"); 807 break; 808 case TEAM_BLUE: 809 ForceClientSkin(client, model, "blue"); 810 // ForceClientSkin(client, headModel, "blue"); 811 break; 812 } 813 // don't ever use a default skin in teamplay, it would just waste memory 814 // however bots will always join a team but they spawn in as spectator 815 if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { 816 ForceClientSkin(client, model, "red"); 817 // ForceClientSkin(client, headModel, "red"); 818 } 819 */ 820 821 #ifdef MISSIONPACK 822 if (g_gametype.integer >= GT_TEAM) { 823 client->pers.teamInfo = qtrue; 824 } else { 825 s = Info_ValueForKey( userinfo, "teamoverlay" ); 826 if ( ! *s || atoi( s ) != 0 ) { 827 client->pers.teamInfo = qtrue; 828 } else { 829 client->pers.teamInfo = qfalse; 830 } 831 } 832 #else 833 // teamInfo 834 s = Info_ValueForKey( userinfo, "teamoverlay" ); 835 if ( ! *s || atoi( s ) != 0 ) { 836 client->pers.teamInfo = qtrue; 837 } else { 838 client->pers.teamInfo = qfalse; 839 } 840 #endif 841 /* 842 s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); 843 if ( !*s || atoi( s ) == 0 ) { 844 client->pers.pmoveFixed = qfalse; 845 } 846 else { 847 client->pers.pmoveFixed = qtrue; 848 } 849 */ 850 851 // team task (0 = none, 1 = offence, 2 = defence) 852 teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); 853 // team Leader (1 = leader, 0 is normal player) 854 teamLeader = client->sess.teamLeader; 855 856 // colors 857 strcpy(c1, Info_ValueForKey( userinfo, "color1" )); 858 strcpy(c2, Info_ValueForKey( userinfo, "color2" )); 859 860 strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); 861 strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); 862 863 // send over a subset of the userinfo keys so other clients can 864 // print scoreboards, display models, and play custom sounds 865 if ( ent->r.svFlags & SVF_BOT ) { 866 s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", 867 client->pers.netname, team, model, headModel, c1, c2, 868 client->pers.maxHealth, client->sess.wins, client->sess.losses, 869 Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); 870 } else { 871 s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", 872 client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, 873 client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); 874 } 875 876 trap_SetConfigstring( CS_PLAYERS+clientNum, s ); 877 878 // this is not the userinfo, more like the configstring actually 879 G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); 880 } 881 882 883 /* 884 =========== 885 ClientConnect 886 887 Called when a player begins connecting to the server. 888 Called again for every map change or tournement restart. 889 890 The session information will be valid after exit. 891 892 Return NULL if the client should be allowed, otherwise return 893 a string with the reason for denial. 894 895 Otherwise, the client will be sent the current gamestate 896 and will eventually get to ClientBegin. 897 898 firstTime will be qtrue the very first time a client connects 899 to the server machine, but qfalse on map changes and tournement 900 restarts. 901 ============ 902 */ 903 char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { 904 char *value; 905 // char *areabits; 906 gclient_t *client; 907 char userinfo[MAX_INFO_STRING]; 908 gentity_t *ent; 909 910 ent = &g_entities[ clientNum ]; 911 912 trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); 913 914 // IP filtering 915 // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 916 // recommanding PB based IP / GUID banning, the builtin system is pretty limited 917 // check to see if they are on the banned IP list 918 value = Info_ValueForKey (userinfo, "ip"); 919 if ( G_FilterPacket( value ) ) { 920 return "You are banned from this server."; 921 } 922 923 // we don't check password for bots and local client 924 // NOTE: local client <-> "ip" "localhost" 925 // this means this client is not running in our current process 926 if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { 927 // check for a password 928 value = Info_ValueForKey (userinfo, "password"); 929 if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && 930 strcmp( g_password.string, value) != 0) { 931 return "Invalid password"; 932 } 933 } 934 935 // they can connect 936 ent->client = level.clients + clientNum; 937 client = ent->client; 938 939 // areabits = client->areabits; 940 941 memset( client, 0, sizeof(*client) ); 942 943 client->pers.connected = CON_CONNECTING; 944 945 // read or initialize the session data 946 if ( firstTime || level.newSession ) { 947 G_InitSessionData( client, userinfo ); 948 } 949 G_ReadSessionData( client ); 950 951 if( isBot ) { 952 ent->r.svFlags |= SVF_BOT; 953 ent->inuse = qtrue; 954 if( !G_BotConnect( clientNum, !firstTime ) ) { 955 return "BotConnectfailed"; 956 } 957 } 958 959 // get and distribute relevent paramters 960 G_LogPrintf( "ClientConnect: %i\n", clientNum ); 961 ClientUserinfoChanged( clientNum ); 962 963 // don't do the "xxx connected" messages if they were caried over from previous level 964 if ( firstTime ) { 965 trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); 966 } 967 968 if ( g_gametype.integer >= GT_TEAM && 969 client->sess.sessionTeam != TEAM_SPECTATOR ) { 970 BroadcastTeamChange( client, -1 ); 971 } 972 973 // count current clients and rank for scoreboard 974 CalculateRanks(); 975 976 // for statistics 977 // client->areabits = areabits; 978 // if ( !client->areabits ) 979 // client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); 980 981 return NULL; 982 } 983 984 /* 985 =========== 986 ClientBegin 987 988 called when a client has finished connecting, and is ready 989 to be placed into the level. This will happen every level load, 990 and on transition between teams, but doesn't happen on respawns 991 ============ 992 */ 993 void ClientBegin( int clientNum ) { 994 gentity_t *ent; 995 gclient_t *client; 996 gentity_t *tent; 997 int flags; 998 999 ent = g_entities + clientNum; 1000 1001 client = level.clients + clientNum; 1002 1003 if ( ent->r.linked ) { 1004 trap_UnlinkEntity( ent ); 1005 } 1006 G_InitGentity( ent ); 1007 ent->touch = 0; 1008 ent->pain = 0; 1009 ent->client = client; 1010 1011 client->pers.connected = CON_CONNECTED; 1012 client->pers.enterTime = level.time; 1013 client->pers.teamState.state = TEAM_BEGIN; 1014 1015 // save eflags around this, because changing teams will 1016 // cause this to happen with a valid entity, and we 1017 // want to make sure the teleport bit is set right 1018 // so the viewpoint doesn't interpolate through the 1019 // world to the new position 1020 flags = client->ps.eFlags; 1021 memset( &client->ps, 0, sizeof( client->ps ) ); 1022 client->ps.eFlags = flags; 1023 1024 // locate ent at a spawn point 1025 ClientSpawn( ent ); 1026 1027 if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { 1028 // send event 1029 tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); 1030 tent->s.clientNum = ent->s.clientNum; 1031 1032 if ( g_gametype.integer != GT_TOURNAMENT ) { 1033 trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); 1034 } 1035 } 1036 G_LogPrintf( "ClientBegin: %i\n", clientNum ); 1037 1038 // count current clients and rank for scoreboard 1039 CalculateRanks(); 1040 } 1041 1042 /* 1043 =========== 1044 ClientSpawn 1045 1046 Called every time a client is placed fresh in the world: 1047 after the first ClientBegin, and after each respawn 1048 Initializes all non-persistant parts of playerState 1049 ============ 1050 */ 1051 void ClientSpawn(gentity_t *ent) { 1052 int index; 1053 vec3_t spawn_origin, spawn_angles; 1054 gclient_t *client; 1055 int i; 1056 clientPersistant_t saved; 1057 clientSession_t savedSess; 1058 int persistant[MAX_PERSISTANT]; 1059 gentity_t *spawnPoint; 1060 int flags; 1061 int savedPing; 1062 // char *savedAreaBits; 1063 int accuracy_hits, accuracy_shots; 1064 int eventSequence; 1065 char userinfo[MAX_INFO_STRING]; 1066 1067 index = ent - g_entities; 1068 client = ent->client; 1069 1070 // find a spawn point 1071 // do it before setting health back up, so farthest 1072 // ranging doesn't count this client 1073 if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { 1074 spawnPoint = SelectSpectatorSpawnPoint ( 1075 spawn_origin, spawn_angles); 1076 } else if (g_gametype.integer >= GT_CTF ) { 1077 // all base oriented team games use the CTF spawn points 1078 spawnPoint = SelectCTFSpawnPoint ( 1079 client->sess.sessionTeam, 1080 client->pers.teamState.state, 1081 spawn_origin, spawn_angles); 1082 } else { 1083 do { 1084 // the first spawn should be at a good looking spot 1085 if ( !client->pers.initialSpawn && client->pers.localClient ) { 1086 client->pers.initialSpawn = qtrue; 1087 spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); 1088 } else { 1089 // don't spawn near existing origin if possible 1090 spawnPoint = SelectSpawnPoint ( 1091 client->ps.origin, 1092 spawn_origin, spawn_angles); 1093 } 1094 1095 // Tim needs to prevent bots from spawning at the initial point 1096 // on q3dm0... 1097 if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { 1098 continue; // try again 1099 } 1100 // just to be symetric, we have a nohumans option... 1101 if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { 1102 continue; // try again 1103 } 1104 1105 break; 1106 1107 } while ( 1 ); 1108 } 1109 client->pers.teamState.state = TEAM_ACTIVE; 1110 1111 // always clear the kamikaze flag 1112 ent->s.eFlags &= ~EF_KAMIKAZE; 1113 1114 // toggle the teleport bit so the client knows to not lerp 1115 // and never clear the voted flag 1116 flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); 1117 flags ^= EF_TELEPORT_BIT; 1118 1119 // clear everything but the persistant data 1120 1121 saved = client->pers; 1122 savedSess = client->sess; 1123 savedPing = client->ps.ping; 1124 // savedAreaBits = client->areabits; 1125 accuracy_hits = client->accuracy_hits; 1126 accuracy_shots = client->accuracy_shots; 1127 for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { 1128 persistant[i] = client->ps.persistant[i]; 1129 } 1130 eventSequence = client->ps.eventSequence; 1131 1132 memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? 1133 1134 client->pers = saved; 1135 client->sess = savedSess; 1136 client->ps.ping = savedPing; 1137 // client->areabits = savedAreaBits; 1138 client->accuracy_hits = accuracy_hits; 1139 client->accuracy_shots = accuracy_shots; 1140 client->lastkilled_client = -1; 1141 1142 for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { 1143 client->ps.persistant[i] = persistant[i]; 1144 } 1145 client->ps.eventSequence = eventSequence; 1146 // increment the spawncount so the client will detect the respawn 1147 client->ps.persistant[PERS_SPAWN_COUNT]++; 1148 client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; 1149 1150 client->airOutTime = level.time + 12000; 1151 1152 trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); 1153 // set max health 1154 client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); 1155 if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { 1156 client->pers.maxHealth = 100; 1157 } 1158 // clear entity values 1159 client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; 1160 client->ps.eFlags = flags; 1161 1162 ent->s.groundEntityNum = ENTITYNUM_NONE; 1163 ent->client = &level.clients[index]; 1164 ent->takedamage = qtrue; 1165 ent->inuse = qtrue; 1166 ent->classname = "player"; 1167 ent->r.contents = CONTENTS_BODY; 1168 ent->clipmask = MASK_PLAYERSOLID; 1169 ent->die = player_die; 1170 ent->waterlevel = 0; 1171 ent->watertype = 0; 1172 ent->flags = 0; 1173 1174 VectorCopy (playerMins, ent->r.mins); 1175 VectorCopy (playerMaxs, ent->r.maxs); 1176 1177 client->ps.clientNum = index; 1178 1179 client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); 1180 if ( g_gametype.integer == GT_TEAM ) { 1181 client->ps.ammo[WP_MACHINEGUN] = 50; 1182 } else { 1183 client->ps.ammo[WP_MACHINEGUN] = 100; 1184 } 1185 1186 client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); 1187 client->ps.ammo[WP_GAUNTLET] = -1; 1188 client->ps.ammo[WP_GRAPPLING_HOOK] = -1; 1189 1190 // health will count down towards max_health 1191 ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; 1192 1193 G_SetOrigin( ent, spawn_origin ); 1194 VectorCopy( spawn_origin, client->ps.origin ); 1195 1196 // the respawned flag will be cleared after the attack and jump keys come up 1197 client->ps.pm_flags |= PMF_RESPAWNED; 1198 1199 trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); 1200 SetClientViewAngle( ent, spawn_angles ); 1201 1202 if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { 1203 1204 } else { 1205 G_KillBox( ent ); 1206 trap_LinkEntity (ent); 1207 1208 // force the base weapon up 1209 client->ps.weapon = WP_MACHINEGUN; 1210 client->ps.weaponstate = WEAPON_READY; 1211 1212 } 1213 1214 // don't allow full run speed for a bit 1215 client->ps.pm_flags |= PMF_TIME_KNOCKBACK; 1216 client->ps.pm_time = 100; 1217 1218 client->respawnTime = level.time; 1219 client->inactivityTime = level.time + g_inactivity.integer * 1000; 1220 client->latched_buttons = 0; 1221 1222 // set default animations 1223 client->ps.torsoAnim = TORSO_STAND; 1224 client->ps.legsAnim = LEGS_IDLE; 1225 1226 if ( level.intermissiontime ) { 1227 MoveClientToIntermission( ent ); 1228 } else { 1229 // fire the targets of the spawn point 1230 G_UseTargets( spawnPoint, ent ); 1231 1232 // select the highest weapon number available, after any 1233 // spawn given items have fired 1234 client->ps.weapon = 1; 1235 for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { 1236 if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { 1237 client->ps.weapon = i; 1238 break; 1239 } 1240 } 1241 } 1242 1243 // run a client frame to drop exactly to the floor, 1244 // initialize animations and other things 1245 client->ps.commandTime = level.time - 100; 1246 ent->client->pers.cmd.serverTime = level.time; 1247 ClientThink( ent-g_entities ); 1248 1249 // positively link the client, even if the command times are weird 1250 if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { 1251 BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); 1252 VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); 1253 trap_LinkEntity( ent ); 1254 } 1255 1256 // run the presend to set anything else 1257 ClientEndFrame( ent ); 1258 1259 // clear entity state values 1260 BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); 1261 } 1262 1263 1264 /* 1265 =========== 1266 ClientDisconnect 1267 1268 Called when a player drops from the server. 1269 Will not be called between levels. 1270 1271 This should NOT be called directly by any game logic, 1272 call trap_DropClient(), which will call this and do 1273 server system housekeeping. 1274 ============ 1275 */ 1276 void ClientDisconnect( int clientNum ) { 1277 gentity_t *ent; 1278 gentity_t *tent; 1279 int i; 1280 1281 // cleanup if we are kicking a bot that 1282 // hasn't spawned yet 1283 G_RemoveQueuedBotBegin( clientNum ); 1284 1285 ent = g_entities + clientNum; 1286 if ( !ent->client ) { 1287 return; 1288 } 1289 1290 // stop any following clients 1291 for ( i = 0 ; i < level.maxclients ; i++ ) { 1292 if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR 1293 && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW 1294 && level.clients[i].sess.spectatorClient == clientNum ) { 1295 StopFollowing( &g_entities[i] ); 1296 } 1297 } 1298 1299 // send effect if they were completely connected 1300 if ( ent->client->pers.connected == CON_CONNECTED 1301 && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { 1302 tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); 1303 tent->s.clientNum = ent->s.clientNum; 1304 1305 // They don't get to take powerups with them! 1306 // Especially important for stuff like CTF flags 1307 TossClientItems( ent ); 1308 #ifdef MISSIONPACK 1309 TossClientPersistantPowerups( ent ); 1310 if( g_gametype.integer == GT_HARVESTER ) { 1311 TossClientCubes( ent ); 1312 } 1313 #endif 1314 1315 } 1316 1317 G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); 1318 1319 // if we are playing in tourney mode and losing, give a win to the other player 1320 if ( (g_gametype.integer == GT_TOURNAMENT ) 1321 && !level.intermissiontime 1322 && !level.warmupTime && level.sortedClients[1] == clientNum ) { 1323 level.clients[ level.sortedClients[0] ].sess.wins++; 1324 ClientUserinfoChanged( level.sortedClients[0] ); 1325 } 1326 1327 trap_UnlinkEntity (ent); 1328 ent->s.modelindex = 0; 1329 ent->inuse = qfalse; 1330 ent->classname = "disconnected"; 1331 ent->client->pers.connected = CON_DISCONNECTED; 1332 ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; 1333 ent->client->sess.sessionTeam = TEAM_FREE; 1334 1335 trap_SetConfigstring( CS_PLAYERS + clientNum, ""); 1336 1337 CalculateRanks(); 1338 1339 if ( ent->r.svFlags & SVF_BOT ) { 1340 BotAIShutdownClient( clientNum, qfalse ); 1341 } 1342 } 1343 1344