g_active.c (31488B)
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 24 #include "g_local.h" 25 26 27 /* 28 =============== 29 G_DamageFeedback 30 31 Called just before a snapshot is sent to the given player. 32 Totals up all damage and generates both the player_state_t 33 damage values to that client for pain blends and kicks, and 34 global pain sound events for all clients. 35 =============== 36 */ 37 void P_DamageFeedback( gentity_t *player ) { 38 gclient_t *client; 39 float count; 40 vec3_t angles; 41 42 client = player->client; 43 if ( client->ps.pm_type == PM_DEAD ) { 44 return; 45 } 46 47 // total points of damage shot at the player this frame 48 count = client->damage_blood + client->damage_armor; 49 if ( count == 0 ) { 50 return; // didn't take any damage 51 } 52 53 if ( count > 255 ) { 54 count = 255; 55 } 56 57 // send the information to the client 58 59 // world damage (falling, slime, etc) uses a special code 60 // to make the blend blob centered instead of positional 61 if ( client->damage_fromWorld ) { 62 client->ps.damagePitch = 255; 63 client->ps.damageYaw = 255; 64 65 client->damage_fromWorld = qfalse; 66 } else { 67 vectoangles( client->damage_from, angles ); 68 client->ps.damagePitch = angles[PITCH]/360.0 * 256; 69 client->ps.damageYaw = angles[YAW]/360.0 * 256; 70 } 71 72 // play an apropriate pain sound 73 if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { 74 player->pain_debounce_time = level.time + 700; 75 G_AddEvent( player, EV_PAIN, player->health ); 76 client->ps.damageEvent++; 77 } 78 79 80 client->ps.damageCount = count; 81 82 // 83 // clear totals 84 // 85 client->damage_blood = 0; 86 client->damage_armor = 0; 87 client->damage_knockback = 0; 88 } 89 90 91 92 /* 93 ============= 94 P_WorldEffects 95 96 Check for lava / slime contents and drowning 97 ============= 98 */ 99 void P_WorldEffects( gentity_t *ent ) { 100 qboolean envirosuit; 101 int waterlevel; 102 103 if ( ent->client->noclip ) { 104 ent->client->airOutTime = level.time + 12000; // don't need air 105 return; 106 } 107 108 waterlevel = ent->waterlevel; 109 110 envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; 111 112 // 113 // check for drowning 114 // 115 if ( waterlevel == 3 ) { 116 // envirosuit give air 117 if ( envirosuit ) { 118 ent->client->airOutTime = level.time + 10000; 119 } 120 121 // if out of air, start drowning 122 if ( ent->client->airOutTime < level.time) { 123 // drown! 124 ent->client->airOutTime += 1000; 125 if ( ent->health > 0 ) { 126 // take more damage the longer underwater 127 ent->damage += 2; 128 if (ent->damage > 15) 129 ent->damage = 15; 130 131 // play a gurp sound instead of a normal pain sound 132 if (ent->health <= ent->damage) { 133 G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); 134 } else if (rand()&1) { 135 G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); 136 } else { 137 G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); 138 } 139 140 // don't play a normal pain sound 141 ent->pain_debounce_time = level.time + 200; 142 143 G_Damage (ent, NULL, NULL, NULL, NULL, 144 ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); 145 } 146 } 147 } else { 148 ent->client->airOutTime = level.time + 12000; 149 ent->damage = 2; 150 } 151 152 // 153 // check for sizzle damage (move to pmove?) 154 // 155 if (waterlevel && 156 (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { 157 if (ent->health > 0 158 && ent->pain_debounce_time <= level.time ) { 159 160 if ( envirosuit ) { 161 G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); 162 } else { 163 if (ent->watertype & CONTENTS_LAVA) { 164 G_Damage (ent, NULL, NULL, NULL, NULL, 165 30*waterlevel, 0, MOD_LAVA); 166 } 167 168 if (ent->watertype & CONTENTS_SLIME) { 169 G_Damage (ent, NULL, NULL, NULL, NULL, 170 10*waterlevel, 0, MOD_SLIME); 171 } 172 } 173 } 174 } 175 } 176 177 178 179 /* 180 =============== 181 G_SetClientSound 182 =============== 183 */ 184 void G_SetClientSound( gentity_t *ent ) { 185 #ifdef MISSIONPACK 186 if( ent->s.eFlags & EF_TICKING ) { 187 ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); 188 } 189 else 190 #endif 191 if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { 192 ent->client->ps.loopSound = level.snd_fry; 193 } else { 194 ent->client->ps.loopSound = 0; 195 } 196 } 197 198 199 200 //============================================================== 201 202 /* 203 ============== 204 ClientImpacts 205 ============== 206 */ 207 void ClientImpacts( gentity_t *ent, pmove_t *pm ) { 208 int i, j; 209 trace_t trace; 210 gentity_t *other; 211 212 memset( &trace, 0, sizeof( trace ) ); 213 for (i=0 ; i<pm->numtouch ; i++) { 214 for (j=0 ; j<i ; j++) { 215 if (pm->touchents[j] == pm->touchents[i] ) { 216 break; 217 } 218 } 219 if (j != i) { 220 continue; // duplicated 221 } 222 other = &g_entities[ pm->touchents[i] ]; 223 224 if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { 225 ent->touch( ent, other, &trace ); 226 } 227 228 if ( !other->touch ) { 229 continue; 230 } 231 232 other->touch( other, ent, &trace ); 233 } 234 235 } 236 237 /* 238 ============ 239 G_TouchTriggers 240 241 Find all trigger entities that ent's current position touches. 242 Spectators will only interact with teleporters. 243 ============ 244 */ 245 void G_TouchTriggers( gentity_t *ent ) { 246 int i, num; 247 int touch[MAX_GENTITIES]; 248 gentity_t *hit; 249 trace_t trace; 250 vec3_t mins, maxs; 251 static vec3_t range = { 40, 40, 52 }; 252 253 if ( !ent->client ) { 254 return; 255 } 256 257 // dead clients don't activate triggers! 258 if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { 259 return; 260 } 261 262 VectorSubtract( ent->client->ps.origin, range, mins ); 263 VectorAdd( ent->client->ps.origin, range, maxs ); 264 265 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); 266 267 // can't use ent->absmin, because that has a one unit pad 268 VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); 269 VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); 270 271 for ( i=0 ; i<num ; i++ ) { 272 hit = &g_entities[touch[i]]; 273 274 if ( !hit->touch && !ent->touch ) { 275 continue; 276 } 277 if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { 278 continue; 279 } 280 281 // ignore most entities if a spectator 282 if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { 283 if ( hit->s.eType != ET_TELEPORT_TRIGGER && 284 // this is ugly but adding a new ET_? type will 285 // most likely cause network incompatibilities 286 hit->touch != Touch_DoorTrigger) { 287 continue; 288 } 289 } 290 291 // use seperate code for determining if an item is picked up 292 // so you don't have to actually contact its bounding box 293 if ( hit->s.eType == ET_ITEM ) { 294 if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { 295 continue; 296 } 297 } else { 298 if ( !trap_EntityContact( mins, maxs, hit ) ) { 299 continue; 300 } 301 } 302 303 memset( &trace, 0, sizeof(trace) ); 304 305 if ( hit->touch ) { 306 hit->touch (hit, ent, &trace); 307 } 308 309 if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { 310 ent->touch( ent, hit, &trace ); 311 } 312 } 313 314 // if we didn't touch a jump pad this pmove frame 315 if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { 316 ent->client->ps.jumppad_frame = 0; 317 ent->client->ps.jumppad_ent = 0; 318 } 319 } 320 321 /* 322 ================= 323 SpectatorThink 324 ================= 325 */ 326 void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { 327 pmove_t pm; 328 gclient_t *client; 329 330 client = ent->client; 331 332 if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { 333 client->ps.pm_type = PM_SPECTATOR; 334 client->ps.speed = 400; // faster than normal 335 336 // set up for pmove 337 memset (&pm, 0, sizeof(pm)); 338 pm.ps = &client->ps; 339 pm.cmd = *ucmd; 340 pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies 341 pm.trace = trap_Trace; 342 pm.pointcontents = trap_PointContents; 343 344 // perform a pmove 345 Pmove (&pm); 346 // save results of pmove 347 VectorCopy( client->ps.origin, ent->s.origin ); 348 349 G_TouchTriggers( ent ); 350 trap_UnlinkEntity( ent ); 351 } 352 353 client->oldbuttons = client->buttons; 354 client->buttons = ucmd->buttons; 355 356 // attack button cycles through spectators 357 if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { 358 Cmd_FollowCycle_f( ent, 1 ); 359 } 360 } 361 362 363 364 /* 365 ================= 366 ClientInactivityTimer 367 368 Returns qfalse if the client is dropped 369 ================= 370 */ 371 qboolean ClientInactivityTimer( gclient_t *client ) { 372 if ( ! g_inactivity.integer ) { 373 // give everyone some time, so if the operator sets g_inactivity during 374 // gameplay, everyone isn't kicked 375 client->inactivityTime = level.time + 60 * 1000; 376 client->inactivityWarning = qfalse; 377 } else if ( client->pers.cmd.forwardmove || 378 client->pers.cmd.rightmove || 379 client->pers.cmd.upmove || 380 (client->pers.cmd.buttons & BUTTON_ATTACK) ) { 381 client->inactivityTime = level.time + g_inactivity.integer * 1000; 382 client->inactivityWarning = qfalse; 383 } else if ( !client->pers.localClient ) { 384 if ( level.time > client->inactivityTime ) { 385 trap_DropClient( client - level.clients, "Dropped due to inactivity" ); 386 return qfalse; 387 } 388 if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { 389 client->inactivityWarning = qtrue; 390 trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); 391 } 392 } 393 return qtrue; 394 } 395 396 /* 397 ================== 398 ClientTimerActions 399 400 Actions that happen once a second 401 ================== 402 */ 403 void ClientTimerActions( gentity_t *ent, int msec ) { 404 gclient_t *client; 405 #ifdef MISSIONPACK 406 int maxHealth; 407 #endif 408 409 client = ent->client; 410 client->timeResidual += msec; 411 412 while ( client->timeResidual >= 1000 ) { 413 client->timeResidual -= 1000; 414 415 // regenerate 416 #ifdef MISSIONPACK 417 if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { 418 maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; 419 } 420 else if ( client->ps.powerups[PW_REGEN] ) { 421 maxHealth = client->ps.stats[STAT_MAX_HEALTH]; 422 } 423 else { 424 maxHealth = 0; 425 } 426 if( maxHealth ) { 427 if ( ent->health < maxHealth ) { 428 ent->health += 15; 429 if ( ent->health > maxHealth * 1.1 ) { 430 ent->health = maxHealth * 1.1; 431 } 432 G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); 433 } else if ( ent->health < maxHealth * 2) { 434 ent->health += 5; 435 if ( ent->health > maxHealth * 2 ) { 436 ent->health = maxHealth * 2; 437 } 438 G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); 439 } 440 #else 441 if ( client->ps.powerups[PW_REGEN] ) { 442 if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { 443 ent->health += 15; 444 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { 445 ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; 446 } 447 G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); 448 } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { 449 ent->health += 5; 450 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { 451 ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; 452 } 453 G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); 454 } 455 #endif 456 } else { 457 // count down health when over max 458 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { 459 ent->health--; 460 } 461 } 462 463 // count down armor when over max 464 if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { 465 client->ps.stats[STAT_ARMOR]--; 466 } 467 } 468 #ifdef MISSIONPACK 469 if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { 470 int w, max, inc, t, i; 471 int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; 472 int weapCount = sizeof(weapList) / sizeof(int); 473 // 474 for (i = 0; i < weapCount; i++) { 475 w = weapList[i]; 476 477 switch(w) { 478 case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; 479 case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; 480 case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; 481 case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; 482 case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; 483 case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; 484 case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; 485 case WP_BFG: max = 10; inc = 1; t = 4000; break; 486 case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; 487 case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; 488 case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; 489 default: max = 0; inc = 0; t = 1000; break; 490 } 491 client->ammoTimes[w] += msec; 492 if ( client->ps.ammo[w] >= max ) { 493 client->ammoTimes[w] = 0; 494 } 495 if ( client->ammoTimes[w] >= t ) { 496 while ( client->ammoTimes[w] >= t ) 497 client->ammoTimes[w] -= t; 498 client->ps.ammo[w] += inc; 499 if ( client->ps.ammo[w] > max ) { 500 client->ps.ammo[w] = max; 501 } 502 } 503 } 504 } 505 #endif 506 } 507 508 /* 509 ==================== 510 ClientIntermissionThink 511 ==================== 512 */ 513 void ClientIntermissionThink( gclient_t *client ) { 514 client->ps.eFlags &= ~EF_TALK; 515 client->ps.eFlags &= ~EF_FIRING; 516 517 // the level will exit when everyone wants to or after timeouts 518 519 // swap and latch button actions 520 client->oldbuttons = client->buttons; 521 client->buttons = client->pers.cmd.buttons; 522 if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { 523 // this used to be an ^1 but once a player says ready, it should stick 524 client->readyToExit = 1; 525 } 526 } 527 528 529 /* 530 ================ 531 ClientEvents 532 533 Events will be passed on to the clients for presentation, 534 but any server game effects are handled here 535 ================ 536 */ 537 void ClientEvents( gentity_t *ent, int oldEventSequence ) { 538 int i, j; 539 int event; 540 gclient_t *client; 541 int damage; 542 vec3_t dir; 543 vec3_t origin, angles; 544 // qboolean fired; 545 gitem_t *item; 546 gentity_t *drop; 547 548 client = ent->client; 549 550 if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { 551 oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; 552 } 553 for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { 554 event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; 555 556 switch ( event ) { 557 case EV_FALL_MEDIUM: 558 case EV_FALL_FAR: 559 if ( ent->s.eType != ET_PLAYER ) { 560 break; // not in the player model 561 } 562 if ( g_dmflags.integer & DF_NO_FALLING ) { 563 break; 564 } 565 if ( event == EV_FALL_FAR ) { 566 damage = 10; 567 } else { 568 damage = 5; 569 } 570 VectorSet (dir, 0, 0, 1); 571 ent->pain_debounce_time = level.time + 200; // no normal pain sound 572 G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); 573 break; 574 575 case EV_FIRE_WEAPON: 576 FireWeapon( ent ); 577 break; 578 579 case EV_USE_ITEM1: // teleporter 580 // drop flags in CTF 581 item = NULL; 582 j = 0; 583 584 if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { 585 item = BG_FindItemForPowerup( PW_REDFLAG ); 586 j = PW_REDFLAG; 587 } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { 588 item = BG_FindItemForPowerup( PW_BLUEFLAG ); 589 j = PW_BLUEFLAG; 590 } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { 591 item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); 592 j = PW_NEUTRALFLAG; 593 } 594 595 if ( item ) { 596 drop = Drop_Item( ent, item, 0 ); 597 // decide how many seconds it has left 598 drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; 599 if ( drop->count < 1 ) { 600 drop->count = 1; 601 } 602 603 ent->client->ps.powerups[ j ] = 0; 604 } 605 606 #ifdef MISSIONPACK 607 if ( g_gametype.integer == GT_HARVESTER ) { 608 if ( ent->client->ps.generic1 > 0 ) { 609 if ( ent->client->sess.sessionTeam == TEAM_RED ) { 610 item = BG_FindItem( "Blue Cube" ); 611 } else { 612 item = BG_FindItem( "Red Cube" ); 613 } 614 if ( item ) { 615 for ( j = 0; j < ent->client->ps.generic1; j++ ) { 616 drop = Drop_Item( ent, item, 0 ); 617 if ( ent->client->sess.sessionTeam == TEAM_RED ) { 618 drop->spawnflags = TEAM_BLUE; 619 } else { 620 drop->spawnflags = TEAM_RED; 621 } 622 } 623 } 624 ent->client->ps.generic1 = 0; 625 } 626 } 627 #endif 628 SelectSpawnPoint( ent->client->ps.origin, origin, angles ); 629 TeleportPlayer( ent, origin, angles ); 630 break; 631 632 case EV_USE_ITEM2: // medkit 633 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; 634 635 break; 636 637 #ifdef MISSIONPACK 638 case EV_USE_ITEM3: // kamikaze 639 // make sure the invulnerability is off 640 ent->client->invulnerabilityTime = 0; 641 // start the kamikze 642 G_StartKamikaze( ent ); 643 break; 644 645 case EV_USE_ITEM4: // portal 646 if( ent->client->portalID ) { 647 DropPortalSource( ent ); 648 } 649 else { 650 DropPortalDestination( ent ); 651 } 652 break; 653 case EV_USE_ITEM5: // invulnerability 654 ent->client->invulnerabilityTime = level.time + 10000; 655 break; 656 #endif 657 658 default: 659 break; 660 } 661 } 662 663 } 664 665 #ifdef MISSIONPACK 666 /* 667 ============== 668 StuckInOtherClient 669 ============== 670 */ 671 static int StuckInOtherClient(gentity_t *ent) { 672 int i; 673 gentity_t *ent2; 674 675 ent2 = &g_entities[0]; 676 for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { 677 if ( ent2 == ent ) { 678 continue; 679 } 680 if ( !ent2->inuse ) { 681 continue; 682 } 683 if ( !ent2->client ) { 684 continue; 685 } 686 if ( ent2->health <= 0 ) { 687 continue; 688 } 689 // 690 if (ent2->r.absmin[0] > ent->r.absmax[0]) 691 continue; 692 if (ent2->r.absmin[1] > ent->r.absmax[1]) 693 continue; 694 if (ent2->r.absmin[2] > ent->r.absmax[2]) 695 continue; 696 if (ent2->r.absmax[0] < ent->r.absmin[0]) 697 continue; 698 if (ent2->r.absmax[1] < ent->r.absmin[1]) 699 continue; 700 if (ent2->r.absmax[2] < ent->r.absmin[2]) 701 continue; 702 return qtrue; 703 } 704 return qfalse; 705 } 706 #endif 707 708 void BotTestSolid(vec3_t origin); 709 710 /* 711 ============== 712 SendPendingPredictableEvents 713 ============== 714 */ 715 void SendPendingPredictableEvents( playerState_t *ps ) { 716 gentity_t *t; 717 int event, seq; 718 int extEvent, number; 719 720 // if there are still events pending 721 if ( ps->entityEventSequence < ps->eventSequence ) { 722 // create a temporary entity for this event which is sent to everyone 723 // except the client who generated the event 724 seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); 725 event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); 726 // set external event to zero before calling BG_PlayerStateToEntityState 727 extEvent = ps->externalEvent; 728 ps->externalEvent = 0; 729 // create temporary entity for event 730 t = G_TempEntity( ps->origin, event ); 731 number = t->s.number; 732 BG_PlayerStateToEntityState( ps, &t->s, qtrue ); 733 t->s.number = number; 734 t->s.eType = ET_EVENTS + event; 735 t->s.eFlags |= EF_PLAYER_EVENT; 736 t->s.otherEntityNum = ps->clientNum; 737 // send to everyone except the client who generated the event 738 t->r.svFlags |= SVF_NOTSINGLECLIENT; 739 t->r.singleClient = ps->clientNum; 740 // set back external event 741 ps->externalEvent = extEvent; 742 } 743 } 744 745 /* 746 ============== 747 ClientThink 748 749 This will be called once for each client frame, which will 750 usually be a couple times for each server frame on fast clients. 751 752 If "g_synchronousClients 1" is set, this will be called exactly 753 once for each server frame, which makes for smooth demo recording. 754 ============== 755 */ 756 void ClientThink_real( gentity_t *ent ) { 757 gclient_t *client; 758 pmove_t pm; 759 int oldEventSequence; 760 int msec; 761 usercmd_t *ucmd; 762 763 client = ent->client; 764 765 // don't think if the client is not yet connected (and thus not yet spawned in) 766 if (client->pers.connected != CON_CONNECTED) { 767 return; 768 } 769 // mark the time, so the connection sprite can be removed 770 ucmd = &ent->client->pers.cmd; 771 772 // sanity check the command time to prevent speedup cheating 773 if ( ucmd->serverTime > level.time + 200 ) { 774 ucmd->serverTime = level.time + 200; 775 // G_Printf("serverTime <<<<<\n" ); 776 } 777 if ( ucmd->serverTime < level.time - 1000 ) { 778 ucmd->serverTime = level.time - 1000; 779 // G_Printf("serverTime >>>>>\n" ); 780 } 781 782 msec = ucmd->serverTime - client->ps.commandTime; 783 // following others may result in bad times, but we still want 784 // to check for follow toggles 785 if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { 786 return; 787 } 788 if ( msec > 200 ) { 789 msec = 200; 790 } 791 792 if ( pmove_msec.integer < 8 ) { 793 trap_Cvar_Set("pmove_msec", "8"); 794 } 795 else if (pmove_msec.integer > 33) { 796 trap_Cvar_Set("pmove_msec", "33"); 797 } 798 799 if ( pmove_fixed.integer || client->pers.pmoveFixed ) { 800 ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; 801 //if (ucmd->serverTime - client->ps.commandTime <= 0) 802 // return; 803 } 804 805 // 806 // check for exiting intermission 807 // 808 if ( level.intermissiontime ) { 809 ClientIntermissionThink( client ); 810 return; 811 } 812 813 // spectators don't do much 814 if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { 815 if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { 816 return; 817 } 818 SpectatorThink( ent, ucmd ); 819 return; 820 } 821 822 // check for inactivity timer, but never drop the local client of a non-dedicated server 823 if ( !ClientInactivityTimer( client ) ) { 824 return; 825 } 826 827 // clear the rewards if time 828 if ( level.time > client->rewardTime ) { 829 client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); 830 } 831 832 if ( client->noclip ) { 833 client->ps.pm_type = PM_NOCLIP; 834 } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { 835 client->ps.pm_type = PM_DEAD; 836 } else { 837 client->ps.pm_type = PM_NORMAL; 838 } 839 840 client->ps.gravity = g_gravity.value; 841 842 // set speed 843 client->ps.speed = g_speed.value; 844 845 #ifdef MISSIONPACK 846 if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { 847 client->ps.speed *= 1.5; 848 } 849 else 850 #endif 851 if ( client->ps.powerups[PW_HASTE] ) { 852 client->ps.speed *= 1.3; 853 } 854 855 // Let go of the hook if we aren't firing 856 if ( client->ps.weapon == WP_GRAPPLING_HOOK && 857 client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { 858 Weapon_HookFree(client->hook); 859 } 860 861 // set up for pmove 862 oldEventSequence = client->ps.eventSequence; 863 864 memset (&pm, 0, sizeof(pm)); 865 866 // check for the hit-scan gauntlet, don't let the action 867 // go through as an attack unless it actually hits something 868 if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && 869 ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { 870 pm.gauntletHit = CheckGauntletAttack( ent ); 871 } 872 873 if ( ent->flags & FL_FORCE_GESTURE ) { 874 ent->flags &= ~FL_FORCE_GESTURE; 875 ent->client->pers.cmd.buttons |= BUTTON_GESTURE; 876 } 877 878 #ifdef MISSIONPACK 879 // check for invulnerability expansion before doing the Pmove 880 if (client->ps.powerups[PW_INVULNERABILITY] ) { 881 if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { 882 vec3_t mins = { -42, -42, -42 }; 883 vec3_t maxs = { 42, 42, 42 }; 884 vec3_t oldmins, oldmaxs; 885 886 VectorCopy (ent->r.mins, oldmins); 887 VectorCopy (ent->r.maxs, oldmaxs); 888 // expand 889 VectorCopy (mins, ent->r.mins); 890 VectorCopy (maxs, ent->r.maxs); 891 trap_LinkEntity(ent); 892 // check if this would get anyone stuck in this player 893 if ( !StuckInOtherClient(ent) ) { 894 // set flag so the expanded size will be set in PM_CheckDuck 895 client->ps.pm_flags |= PMF_INVULEXPAND; 896 } 897 // set back 898 VectorCopy (oldmins, ent->r.mins); 899 VectorCopy (oldmaxs, ent->r.maxs); 900 trap_LinkEntity(ent); 901 } 902 } 903 #endif 904 905 pm.ps = &client->ps; 906 pm.cmd = *ucmd; 907 if ( pm.ps->pm_type == PM_DEAD ) { 908 pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; 909 } 910 else if ( ent->r.svFlags & SVF_BOT ) { 911 pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; 912 } 913 else { 914 pm.tracemask = MASK_PLAYERSOLID; 915 } 916 pm.trace = trap_Trace; 917 pm.pointcontents = trap_PointContents; 918 pm.debugLevel = g_debugMove.integer; 919 pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; 920 921 pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; 922 pm.pmove_msec = pmove_msec.integer; 923 924 VectorCopy( client->ps.origin, client->oldOrigin ); 925 926 #ifdef MISSIONPACK 927 if (level.intermissionQueued != 0 && g_singlePlayer.integer) { 928 if ( level.time - level.intermissionQueued >= 1000 ) { 929 pm.cmd.buttons = 0; 930 pm.cmd.forwardmove = 0; 931 pm.cmd.rightmove = 0; 932 pm.cmd.upmove = 0; 933 if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { 934 trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); 935 } 936 ent->client->ps.pm_type = PM_SPINTERMISSION; 937 } 938 } 939 Pmove (&pm); 940 #else 941 Pmove (&pm); 942 #endif 943 944 // save results of pmove 945 if ( ent->client->ps.eventSequence != oldEventSequence ) { 946 ent->eventTime = level.time; 947 } 948 if (g_smoothClients.integer) { 949 BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); 950 } 951 else { 952 BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); 953 } 954 SendPendingPredictableEvents( &ent->client->ps ); 955 956 if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { 957 client->fireHeld = qfalse; // for grapple 958 } 959 960 // use the snapped origin for linking so it matches client predicted versions 961 VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); 962 963 VectorCopy (pm.mins, ent->r.mins); 964 VectorCopy (pm.maxs, ent->r.maxs); 965 966 ent->waterlevel = pm.waterlevel; 967 ent->watertype = pm.watertype; 968 969 // execute client events 970 ClientEvents( ent, oldEventSequence ); 971 972 // link entity now, after any personal teleporters have been used 973 trap_LinkEntity (ent); 974 if ( !ent->client->noclip ) { 975 G_TouchTriggers( ent ); 976 } 977 978 // NOTE: now copy the exact origin over otherwise clients can be snapped into solid 979 VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); 980 981 //test for solid areas in the AAS file 982 BotTestAAS(ent->r.currentOrigin); 983 984 // touch other objects 985 ClientImpacts( ent, &pm ); 986 987 // save results of triggers and client events 988 if (ent->client->ps.eventSequence != oldEventSequence) { 989 ent->eventTime = level.time; 990 } 991 992 // swap and latch button actions 993 client->oldbuttons = client->buttons; 994 client->buttons = ucmd->buttons; 995 client->latched_buttons |= client->buttons & ~client->oldbuttons; 996 997 // check for respawning 998 if ( client->ps.stats[STAT_HEALTH] <= 0 ) { 999 // wait for the attack button to be pressed 1000 if ( level.time > client->respawnTime ) { 1001 // forcerespawn is to prevent users from waiting out powerups 1002 if ( g_forcerespawn.integer > 0 && 1003 ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { 1004 respawn( ent ); 1005 return; 1006 } 1007 1008 // pressing attack or use is the normal respawn method 1009 if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { 1010 respawn( ent ); 1011 } 1012 } 1013 return; 1014 } 1015 1016 // perform once-a-second actions 1017 ClientTimerActions( ent, msec ); 1018 } 1019 1020 /* 1021 ================== 1022 ClientThink 1023 1024 A new command has arrived from the client 1025 ================== 1026 */ 1027 void ClientThink( int clientNum ) { 1028 gentity_t *ent; 1029 1030 ent = g_entities + clientNum; 1031 trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); 1032 1033 // mark the time we got info, so we can display the 1034 // phone jack if they don't get any for a while 1035 ent->client->lastCmdTime = level.time; 1036 1037 if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { 1038 ClientThink_real( ent ); 1039 } 1040 } 1041 1042 1043 void G_RunClient( gentity_t *ent ) { 1044 if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { 1045 return; 1046 } 1047 ent->client->pers.cmd.serverTime = level.time; 1048 ClientThink_real( ent ); 1049 } 1050 1051 1052 /* 1053 ================== 1054 SpectatorClientEndFrame 1055 1056 ================== 1057 */ 1058 void SpectatorClientEndFrame( gentity_t *ent ) { 1059 gclient_t *cl; 1060 1061 // if we are doing a chase cam or a remote view, grab the latest info 1062 if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { 1063 int clientNum, flags; 1064 1065 clientNum = ent->client->sess.spectatorClient; 1066 1067 // team follow1 and team follow2 go to whatever clients are playing 1068 if ( clientNum == -1 ) { 1069 clientNum = level.follow1; 1070 } else if ( clientNum == -2 ) { 1071 clientNum = level.follow2; 1072 } 1073 if ( clientNum >= 0 ) { 1074 cl = &level.clients[ clientNum ]; 1075 if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { 1076 flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); 1077 ent->client->ps = cl->ps; 1078 ent->client->ps.pm_flags |= PMF_FOLLOW; 1079 ent->client->ps.eFlags = flags; 1080 return; 1081 } else { 1082 // drop them to free spectators unless they are dedicated camera followers 1083 if ( ent->client->sess.spectatorClient >= 0 ) { 1084 ent->client->sess.spectatorState = SPECTATOR_FREE; 1085 ClientBegin( ent->client - level.clients ); 1086 } 1087 } 1088 } 1089 } 1090 1091 if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { 1092 ent->client->ps.pm_flags |= PMF_SCOREBOARD; 1093 } else { 1094 ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; 1095 } 1096 } 1097 1098 /* 1099 ============== 1100 ClientEndFrame 1101 1102 Called at the end of each server frame for each connected client 1103 A fast client will have multiple ClientThink for each ClientEdFrame, 1104 while a slow client may have multiple ClientEndFrame between ClientThink. 1105 ============== 1106 */ 1107 void ClientEndFrame( gentity_t *ent ) { 1108 int i; 1109 clientPersistant_t *pers; 1110 1111 if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { 1112 SpectatorClientEndFrame( ent ); 1113 return; 1114 } 1115 1116 pers = &ent->client->pers; 1117 1118 // turn off any expired powerups 1119 for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { 1120 if ( ent->client->ps.powerups[ i ] < level.time ) { 1121 ent->client->ps.powerups[ i ] = 0; 1122 } 1123 } 1124 1125 #ifdef MISSIONPACK 1126 // set powerup for player animation 1127 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { 1128 ent->client->ps.powerups[PW_GUARD] = level.time; 1129 } 1130 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { 1131 ent->client->ps.powerups[PW_SCOUT] = level.time; 1132 } 1133 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { 1134 ent->client->ps.powerups[PW_DOUBLER] = level.time; 1135 } 1136 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { 1137 ent->client->ps.powerups[PW_AMMOREGEN] = level.time; 1138 } 1139 if ( ent->client->invulnerabilityTime > level.time ) { 1140 ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; 1141 } 1142 #endif 1143 1144 // save network bandwidth 1145 #if 0 1146 if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { 1147 // FIXME: this must change eventually for non-sync demo recording 1148 VectorClear( ent->client->ps.viewangles ); 1149 } 1150 #endif 1151 1152 // 1153 // If the end of unit layout is displayed, don't give 1154 // the player any normal movement attributes 1155 // 1156 if ( level.intermissiontime ) { 1157 return; 1158 } 1159 1160 // burn from lava, etc 1161 P_WorldEffects (ent); 1162 1163 // apply all the damage taken this frame 1164 P_DamageFeedback (ent); 1165 1166 // add the EF_CONNECTION flag if we haven't gotten commands recently 1167 if ( level.time - ent->client->lastCmdTime > 1000 ) { 1168 ent->s.eFlags |= EF_CONNECTION; 1169 } else { 1170 ent->s.eFlags &= ~EF_CONNECTION; 1171 } 1172 1173 ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... 1174 1175 G_SetClientSound (ent); 1176 1177 // set the latest infor 1178 if (g_smoothClients.integer) { 1179 BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); 1180 } 1181 else { 1182 BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); 1183 } 1184 SendPendingPredictableEvents( &ent->client->ps ); 1185 1186 // set the bit for the reachability area the client is currently in 1187 // i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); 1188 // ent->client->areabits[i >> 3] |= 1 << (i & 7); 1189 } 1190 1191