codemp/game/g_active.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 
00004 #include "g_local.h"
00005 #include "bg_saga.h"
00006 
00007 extern void Jedi_Cloak( gentity_t *self );
00008 extern void Jedi_Decloak( gentity_t *self );
00009 
00010 #include "../namespace_begin.h"
00011 qboolean PM_SaberInTransition( int move );
00012 qboolean PM_SaberInStart( int move );
00013 qboolean PM_SaberInReturn( int move );
00014 qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel );
00015 #include "../namespace_end.h"
00016 qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
00017 
00018 extern vmCvar_t g_saberLockRandomNess;
00019 
00020 void P_SetTwitchInfo(gclient_t  *client)
00021 {
00022         client->ps.painTime = level.time;
00023         client->ps.painDirection ^= 1;
00024 }
00025 
00026 /*
00027 ===============
00028 G_DamageFeedback
00029 
00030 Called just before a snapshot is sent to the given player.
00031 Totals up all damage and generates both the player_state_t
00032 damage values to that client for pain blends and kicks, and
00033 global pain sound events for all clients.
00034 ===============
00035 */
00036 void P_DamageFeedback( gentity_t *player ) {
00037         gclient_t       *client;
00038         float   count;
00039         vec3_t  angles;
00040 
00041         client = player->client;
00042         if ( client->ps.pm_type == PM_DEAD ) {
00043                 return;
00044         }
00045 
00046         // total points of damage shot at the player this frame
00047         count = client->damage_blood + client->damage_armor;
00048         if ( count == 0 ) {
00049                 return;         // didn't take any damage
00050         }
00051 
00052         if ( count > 255 ) {
00053                 count = 255;
00054         }
00055 
00056         // send the information to the client
00057 
00058         // world damage (falling, slime, etc) uses a special code
00059         // to make the blend blob centered instead of positional
00060         if ( client->damage_fromWorld ) {
00061                 client->ps.damagePitch = 255;
00062                 client->ps.damageYaw = 255;
00063 
00064                 client->damage_fromWorld = qfalse;
00065         } else {
00066                 vectoangles( client->damage_from, angles );
00067                 client->ps.damagePitch = angles[PITCH]/360.0 * 256;
00068                 client->ps.damageYaw = angles[YAW]/360.0 * 256;
00069 
00070                 //cap them since we can't send negative values in here across the net
00071                 if (client->ps.damagePitch < 0)
00072                 {
00073                         client->ps.damagePitch = 0;
00074                 }
00075                 if (client->ps.damageYaw < 0)
00076                 {
00077                         client->ps.damageYaw = 0;
00078                 }
00079         }
00080 
00081         // play an apropriate pain sound
00082         if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && !(player->s.eFlags & EF_DEAD) ) {
00083 
00084                 // don't do more than two pain sounds a second
00085                 // nmckenzie: also don't make him loud and whiny if he's only getting nicked.
00086                 if ( level.time - client->ps.painTime < 500 || count < 10) {
00087                         return;
00088                 }
00089                 P_SetTwitchInfo(client);
00090                 player->pain_debounce_time = level.time + 700;
00091                 
00092                 G_AddEvent( player, EV_PAIN, player->health );
00093                 client->ps.damageEvent++;
00094 
00095                 if (client->damage_armor && !client->damage_blood)
00096                 {
00097                         client->ps.damageType = 1; //pure shields
00098                 }
00099                 else if (client->damage_armor)
00100                 {
00101                         client->ps.damageType = 2; //shields and health
00102                 }
00103                 else
00104                 {
00105                         client->ps.damageType = 0; //pure health
00106                 }
00107         }
00108 
00109 
00110         client->ps.damageCount = count;
00111 
00112         //
00113         // clear totals
00114         //
00115         client->damage_blood = 0;
00116         client->damage_armor = 0;
00117         client->damage_knockback = 0;
00118 }
00119 
00120 
00121 
00122 /*
00123 =============
00124 P_WorldEffects
00125 
00126 Check for lava / slime contents and drowning
00127 =============
00128 */
00129 void P_WorldEffects( gentity_t *ent ) {
00130         qboolean        envirosuit;
00131         int                     waterlevel;
00132 
00133         if ( ent->client->noclip ) {
00134                 ent->client->airOutTime = level.time + 12000;   // don't need air
00135                 return;
00136         }
00137 
00138         waterlevel = ent->waterlevel;
00139 
00140         envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time;
00141 
00142         //
00143         // check for drowning
00144         //
00145         if ( waterlevel == 3 ) {
00146                 // envirosuit give air
00147                 if ( envirosuit ) {
00148                         ent->client->airOutTime = level.time + 10000;
00149                 }
00150 
00151                 // if out of air, start drowning
00152                 if ( ent->client->airOutTime < level.time) {
00153                         // drown!
00154                         ent->client->airOutTime += 1000;
00155                         if ( ent->health > 0 ) {
00156                                 // take more damage the longer underwater
00157                                 ent->damage += 2;
00158                                 if (ent->damage > 15)
00159                                         ent->damage = 15;
00160 
00161                                 // play a gurp sound instead of a normal pain sound
00162                                 if (ent->health <= ent->damage) {
00163                                         G_Sound(ent, CHAN_VOICE, G_SoundIndex(/*"*drown.wav"*/"sound/player/gurp1.wav"));
00164                                 } else if (rand()&1) {
00165                                         G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav"));
00166                                 } else {
00167                                         G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav"));
00168                                 }
00169 
00170                                 // don't play a normal pain sound
00171                                 ent->pain_debounce_time = level.time + 200;
00172 
00173                                 G_Damage (ent, NULL, NULL, NULL, NULL, 
00174                                         ent->damage, DAMAGE_NO_ARMOR, MOD_WATER);
00175                         }
00176                 }
00177         } else {
00178                 ent->client->airOutTime = level.time + 12000;
00179                 ent->damage = 2;
00180         }
00181 
00182         //
00183         // check for sizzle damage (move to pmove?)
00184         //
00185         if (waterlevel && 
00186                 (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
00187                 if (ent->health > 0
00188                         && ent->pain_debounce_time <= level.time        ) {
00189 
00190                         if ( envirosuit ) {
00191                                 G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 );
00192                         } else {
00193                                 if (ent->watertype & CONTENTS_LAVA) {
00194                                         G_Damage (ent, NULL, NULL, NULL, NULL, 
00195                                                 30*waterlevel, 0, MOD_LAVA);
00196                                 }
00197 
00198                                 if (ent->watertype & CONTENTS_SLIME) {
00199                                         G_Damage (ent, NULL, NULL, NULL, NULL, 
00200                                                 10*waterlevel, 0, MOD_SLIME);
00201                                 }
00202                         }
00203                 }
00204         }
00205 }
00206 
00207 
00208 
00209 
00210 
00211 //==============================================================
00212 extern void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback );
00213 void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf )
00214 {
00215         float magnitude, my_mass;
00216         vec3_t  velocity;
00217         int cont;
00218         qboolean easyBreakBrush = qtrue;
00219 
00220         if( self->client )
00221         {
00222                 VectorCopy( self->client->ps.velocity, velocity );
00223                 if( !self->mass )
00224                 {
00225                         my_mass = 10;
00226                 }
00227                 else
00228                 {
00229                         my_mass = self->mass;
00230                 }
00231         }
00232         else 
00233         {
00234                 VectorCopy( self->s.pos.trDelta, velocity );
00235                 if ( self->s.pos.trType == TR_GRAVITY )
00236                 {
00237                         velocity[2] -= 0.25f * g_gravity.value;
00238                 }
00239                 if( !self->mass )
00240                 {
00241                         my_mass = 1;
00242                 }
00243                 else if ( self->mass <= 10 )
00244                 {
00245                         my_mass = 10;
00246                 }
00247                 else
00248                 {
00249                         my_mass = self->mass;
00250                 }
00251         }
00252 
00253         magnitude = VectorLength( velocity ) * my_mass / 10;
00254 
00255         /*
00256         if(pointcontents(self.absmax)==CONTENT_WATER)//FIXME: or other watertypes
00257                 magnitude/=3;                                                   //water absorbs 2/3 velocity
00258 
00259         if(self.classname=="barrel"&&self.aflag)//rolling barrels are made for impacts!
00260                 magnitude*=3;
00261 
00262         if(self.frozen>0&&magnitude<300&&self.flags&FL_ONGROUND&&loser==world&&self.velocity_z<-20&&self.last_onground+0.3<time)
00263                 magnitude=300;
00264         */
00265         if ( other->material == MAT_GLASS 
00266                 || other->material == MAT_GLASS_METAL 
00267                 || other->material == MAT_GRATE1
00268                 || ((other->flags&FL_BBRUSH)&&(other->spawnflags&8/*THIN*/))
00269                 || (other->r.svFlags&SVF_GLASS_BRUSH) )
00270         {
00271                 easyBreakBrush = qtrue;
00272         }
00273 
00274         if ( !self->client || self->client->ps.lastOnGround+300<level.time || ( self->client->ps.lastOnGround+100 < level.time && easyBreakBrush ) )
00275         {
00276                 vec3_t dir1, dir2;
00277                 float force = 0, dot;
00278 
00279                 if ( easyBreakBrush )
00280                         magnitude *= 2;
00281 
00282                 //damage them
00283                 if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD )
00284                 {
00285                         VectorCopy( velocity, dir1 );
00286                         VectorNormalize( dir1 );
00287                         if( VectorCompare( other->r.currentOrigin, vec3_origin ) )
00288                         {//a brush with no origin
00289                                 VectorCopy ( dir1, dir2 );
00290                         }
00291                         else
00292                         {
00293                                 VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, dir2 );
00294                                 VectorNormalize( dir2 );
00295                         }
00296 
00297                         dot = DotProduct( dir1, dir2 );
00298 
00299                         if ( dot >= 0.2 )
00300                         {
00301                                 force = dot;
00302                         }
00303                         else
00304                         {
00305                                 force = 0;
00306                         }
00307 
00308                         force *= (magnitude/50);
00309 
00310                         cont = trap_PointContents( other->r.absmax, other->s.number );
00311                         if( (cont&CONTENTS_WATER) )//|| (self.classname=="barrel"&&self.aflag))//FIXME: or other watertypes
00312                         {
00313                                 force /= 3;                                                     //water absorbs 2/3 velocity
00314                         }
00315 
00316                         /*
00317                         if(self.frozen>0&&force>10)
00318                                 force=10;
00319                         */
00320 
00321                         if( ( force >= 1 && other->s.number != 0 ) || force >= 10)
00322                         {
00323         /*                      
00324                                 dprint("Damage other (");
00325                                 dprint(loser.classname);
00326                                 dprint("): ");
00327                                 dprint(ftos(force));
00328                                 dprint("\n");
00329         */
00330                                 if ( other->r.svFlags & SVF_GLASS_BRUSH )
00331                                 {
00332                                         other->splashRadius = (float)(self->r.maxs[0] - self->r.mins[0])/4.0f;
00333                                 }
00334                                 if ( other->takedamage )
00335                                 {
00336                                         G_Damage( other, self, self, velocity, self->r.currentOrigin, force, DAMAGE_NO_ARMOR, MOD_CRUSH);//FIXME: MOD_IMPACT
00337                                 }
00338                                 else
00339                                 {
00340                                         G_ApplyKnockback( other, dir2, force );
00341                                 }
00342                         }
00343                 }
00344 
00345                 if ( damageSelf && self->takedamage )
00346                 {
00347                         //Now damage me
00348                         //FIXME: more lenient falling damage, especially for when driving a vehicle
00349                         if ( self->client && self->client->ps.fd.forceJumpZStart )
00350                         {//we were force-jumping
00351                                 if ( self->r.currentOrigin[2] >= self->client->ps.fd.forceJumpZStart )
00352                                 {//we landed at same height or higher than we landed
00353                                         magnitude = 0;
00354                                 }
00355                                 else
00356                                 {//FIXME: take off some of it, at least?
00357                                         magnitude = (self->client->ps.fd.forceJumpZStart-self->r.currentOrigin[2])/3;
00358                                 }
00359                         }
00360                         //if(self.classname!="monster_mezzoman"&&self.netname!="spider")//Cats always land on their feet
00361                                 if( ( magnitude >= 100 + self->health && self->s.number != 0 && self->s.weapon != WP_SABER ) || ( magnitude >= 700 ) )//&& self.safe_time < level.time ))//health here is used to simulate structural integrity
00362                                 {
00363                                         if ( (self->s.weapon == WP_SABER || self->s.number == 0) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 )
00364                                         {//players and jedi take less impact damage
00365                                                 //allow for some lenience on high falls
00366                                                 magnitude /= 2;
00367                                                 /*
00368                                                 if ( self.absorb_time >= time )//crouching on impact absorbs 1/2 the damage
00369                                                 {
00370                                                         magnitude/=2;
00371                                                 }
00372                                                 */
00373                                         }
00374                                         magnitude /= 40;
00375                                         magnitude = magnitude - force/2;//If damage other, subtract half of that damage off of own injury
00376                                         if ( magnitude >= 1 )
00377                                         {
00378                 //FIXME: Put in a thingtype impact sound function
00379                 /*                                      
00380                                                 dprint("Damage self (");
00381                                                 dprint(self.classname);
00382                                                 dprint("): ");
00383                                                 dprint(ftos(magnitude));
00384                                                 dprint("\n");
00385                 */
00386                                                 /*
00387                                                 if ( self.classname=="player_sheep "&& self.flags&FL_ONGROUND && self.velocity_z > -50 )
00388                                                         return;
00389                                                 */
00390                                                 G_Damage( self, NULL, NULL, NULL, self->r.currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT
00391                                         }
00392                                 }
00393                 }
00394 
00395                 //FIXME: slow my velocity some?
00396 
00397                 // NOTENOTE We don't use lastimpact as of yet
00398 //              self->lastImpact = level.time;
00399 
00400                 /*
00401                 if(self.flags&FL_ONGROUND)
00402                         self.last_onground=time;
00403                 */
00404         }
00405 }
00406 
00407 void Client_CheckImpactBBrush( gentity_t *self, gentity_t *other )
00408 {
00409         if ( !other || !other->inuse )
00410         {
00411                 return;
00412         }
00413         if (!self || !self->inuse || !self->client ||
00414                 self->client->tempSpectate >= level.time ||
00415                 self->client->sess.sessionTeam == TEAM_SPECTATOR)
00416         { //hmm.. let's not let spectators ram into breakables.
00417                 return;
00418         }
00419 
00420         /*
00421         if (BG_InSpecialJump(self->client->ps.legsAnim))
00422         { //don't do this either, qa says it creates "balance issues"
00423                 return;
00424         }
00425         */
00426 
00427         if ( other->material == MAT_GLASS 
00428                 || other->material == MAT_GLASS_METAL 
00429                 || other->material == MAT_GRATE1
00430                 || ((other->flags&FL_BBRUSH)&&(other->spawnflags&8/*THIN*/))
00431                 || ((other->flags&FL_BBRUSH)&&(other->health<=10))
00432                 || (other->r.svFlags&SVF_GLASS_BRUSH) )
00433         {//clients only do impact damage against easy-break breakables
00434                 DoImpact( self, other, qfalse );
00435         }
00436 }
00437 
00438 
00439 /*
00440 ===============
00441 G_SetClientSound
00442 ===============
00443 */
00444 void G_SetClientSound( gentity_t *ent ) {
00445         if (ent->client && ent->client->isHacking)
00446         { //loop hacking sound
00447                 ent->client->ps.loopSound = level.snd_hack;
00448                 ent->s.loopIsSoundset = qfalse;
00449         }
00450         else if (ent->client && ent->client->isMedHealed > level.time)
00451         { //loop healing sound
00452                 ent->client->ps.loopSound = level.snd_medHealed;
00453                 ent->s.loopIsSoundset = qfalse;
00454         }
00455         else if (ent->client && ent->client->isMedSupplied > level.time)
00456         { //loop supplying sound
00457                 ent->client->ps.loopSound = level.snd_medSupplied;
00458                 ent->s.loopIsSoundset = qfalse;
00459         }
00460         else if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
00461                 ent->client->ps.loopSound = level.snd_fry;
00462                 ent->s.loopIsSoundset = qfalse;
00463         } else {
00464                 ent->client->ps.loopSound = 0;
00465                 ent->s.loopIsSoundset = qfalse;
00466         }
00467 }
00468 
00469 
00470 
00471 //==============================================================
00472 
00473 /*
00474 ==============
00475 ClientImpacts
00476 ==============
00477 */
00478 void ClientImpacts( gentity_t *ent, pmove_t *pm ) {
00479         int             i, j;
00480         trace_t trace;
00481         gentity_t       *other;
00482 
00483         memset( &trace, 0, sizeof( trace ) );
00484         for (i=0 ; i<pm->numtouch ; i++) {
00485                 for (j=0 ; j<i ; j++) {
00486                         if (pm->touchents[j] == pm->touchents[i] ) {
00487                                 break;
00488                         }
00489                 }
00490                 if (j != i) {
00491                         continue;       // duplicated
00492                 }
00493                 other = &g_entities[ pm->touchents[i] ];
00494 
00495                 if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
00496                         ent->touch( ent, other, &trace );
00497                 }
00498 
00499                 if ( !other->touch ) {
00500                         continue;
00501                 }
00502 
00503                 other->touch( other, ent, &trace );
00504         }
00505 
00506 }
00507 
00508 /*
00509 ============
00510 G_TouchTriggers
00511 
00512 Find all trigger entities that ent's current position touches.
00513 Spectators will only interact with teleporters.
00514 ============
00515 */
00516 void    G_TouchTriggers( gentity_t *ent ) {
00517         int                     i, num;
00518         int                     touch[MAX_GENTITIES];
00519         gentity_t       *hit;
00520         trace_t         trace;
00521         vec3_t          mins, maxs;
00522         static vec3_t   range = { 40, 40, 52 };
00523 
00524         if ( !ent->client ) {
00525                 return;
00526         }
00527 
00528         // dead clients don't activate triggers!
00529         if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
00530                 return;
00531         }
00532 
00533         VectorSubtract( ent->client->ps.origin, range, mins );
00534         VectorAdd( ent->client->ps.origin, range, maxs );
00535 
00536         num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
00537 
00538         // can't use ent->r.absmin, because that has a one unit pad
00539         VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
00540         VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
00541 
00542         for ( i=0 ; i<num ; i++ ) {
00543                 hit = &g_entities[touch[i]];
00544 
00545                 if ( !hit->touch && !ent->touch ) {
00546                         continue;
00547                 }
00548                 if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
00549                         continue;
00550                 }
00551 
00552                 // ignore most entities if a spectator
00553                 if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
00554                         if ( hit->s.eType != ET_TELEPORT_TRIGGER &&
00555                                 // this is ugly but adding a new ET_? type will
00556                                 // most likely cause network incompatibilities
00557                                 hit->touch != Touch_DoorTrigger) {
00558                                 continue;
00559                         }
00560                 }
00561 
00562                 // use seperate code for determining if an item is picked up
00563                 // so you don't have to actually contact its bounding box
00564                 if ( hit->s.eType == ET_ITEM ) {
00565                         if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
00566                                 continue;
00567                         }
00568                 } else {
00569                         if ( !trap_EntityContact( mins, maxs, hit ) ) {
00570                                 continue;
00571                         }
00572                 }
00573 
00574                 memset( &trace, 0, sizeof(trace) );
00575 
00576                 if ( hit->touch ) {
00577                         hit->touch (hit, ent, &trace);
00578                 }
00579 
00580                 if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
00581                         ent->touch( ent, hit, &trace );
00582                 }
00583         }
00584 
00585         // if we didn't touch a jump pad this pmove frame
00586         if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) {
00587                 ent->client->ps.jumppad_frame = 0;
00588                 ent->client->ps.jumppad_ent = 0;
00589         }
00590 }
00591 
00592 
00593 /*
00594 ============
00595 G_MoverTouchTriggers
00596 
00597 Find all trigger entities that ent's current position touches.
00598 Spectators will only interact with teleporters.
00599 ============
00600 */
00601 void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ) 
00602 {
00603         int                     i, num;
00604         float           step, stepSize, dist;
00605         int                     touch[MAX_GENTITIES];
00606         gentity_t       *hit;
00607         trace_t         trace;
00608         vec3_t          mins, maxs, dir, size, checkSpot;
00609         const vec3_t    range = { 40, 40, 52 };
00610 
00611         // non-moving movers don't hit triggers!
00612         if ( !VectorLengthSquared( ent->s.pos.trDelta ) ) 
00613         {
00614                 return;
00615         }
00616 
00617         VectorSubtract( ent->r.mins, ent->r.maxs, size );
00618         stepSize = VectorLength( size );
00619         if ( stepSize < 1 )
00620         {
00621                 stepSize = 1;
00622         }
00623 
00624         VectorSubtract( ent->r.currentOrigin, oldOrg, dir );
00625         dist = VectorNormalize( dir );
00626         for ( step = 0; step <= dist; step += stepSize )
00627         {
00628                 VectorMA( ent->r.currentOrigin, step, dir, checkSpot );
00629                 VectorSubtract( checkSpot, range, mins );
00630                 VectorAdd( checkSpot, range, maxs );
00631 
00632                 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
00633 
00634                 // can't use ent->r.absmin, because that has a one unit pad
00635                 VectorAdd( checkSpot, ent->r.mins, mins );
00636                 VectorAdd( checkSpot, ent->r.maxs, maxs );
00637 
00638                 for ( i=0 ; i<num ; i++ ) 
00639                 {
00640                         hit = &g_entities[touch[i]];
00641 
00642                         if ( hit->s.eType != ET_PUSH_TRIGGER )
00643                         {
00644                                 continue;
00645                         }
00646 
00647                         if ( hit->touch == NULL ) 
00648                         {
00649                                 continue;
00650                         }
00651 
00652                         if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) 
00653                         {
00654                                 continue;
00655                         }
00656 
00657 
00658                         if ( !trap_EntityContact( mins, maxs, hit ) ) 
00659                         {
00660                                 continue;
00661                         }
00662 
00663                         memset( &trace, 0, sizeof(trace) );
00664 
00665                         if ( hit->touch != NULL ) 
00666                         {
00667                                 hit->touch(hit, ent, &trace);
00668                         }
00669                 }
00670         }
00671 }
00672 
00673 /*
00674 =================
00675 SpectatorThink
00676 =================
00677 */
00678 void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) {
00679         pmove_t pm;
00680         gclient_t       *client;
00681 
00682         client = ent->client;
00683 
00684         if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) {
00685                 client->ps.pm_type = PM_SPECTATOR;
00686                 client->ps.speed = 400; // faster than normal
00687                 client->ps.basespeed = 400;
00688 
00689                 //hmm, shouldn't have an anim if you're a spectator, make sure
00690                 //it gets cleared.
00691                 client->ps.legsAnim = 0;
00692                 client->ps.legsTimer = 0;
00693                 client->ps.torsoAnim = 0;
00694                 client->ps.torsoTimer = 0;
00695 
00696                 // set up for pmove
00697                 memset (&pm, 0, sizeof(pm));
00698                 pm.ps = &client->ps;
00699                 pm.cmd = *ucmd;
00700                 pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;       // spectators can fly through bodies
00701                 pm.trace = trap_Trace;
00702                 pm.pointcontents = trap_PointContents;
00703 
00704                 pm.noSpecMove = g_noSpecMove.integer;
00705 
00706                 pm.animations = NULL;
00707                 pm.nonHumanoid = qfalse;
00708 
00709                 //Set up bg entity data
00710                 pm.baseEnt = (bgEntity_t *)g_entities;
00711                 pm.entSize = sizeof(gentity_t);
00712 
00713                 // perform a pmove
00714                 Pmove (&pm);
00715                 // save results of pmove
00716                 VectorCopy( client->ps.origin, ent->s.origin );
00717 
00718                 if (ent->client->tempSpectate < level.time)
00719                 {
00720                         G_TouchTriggers( ent );
00721                 }
00722                 trap_UnlinkEntity( ent );
00723         }
00724 
00725         client->oldbuttons = client->buttons;
00726         client->buttons = ucmd->buttons;
00727 
00728         if (client->tempSpectate < level.time)
00729         {
00730                 // attack button cycles through spectators
00731                 if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) {
00732                         Cmd_FollowCycle_f( ent, 1 );
00733                 }
00734 
00735                 if (client->sess.spectatorState == SPECTATOR_FOLLOW && (ucmd->upmove > 0))
00736                 { //jump now removes you from follow mode
00737                         StopFollowing(ent);
00738                 }
00739         }
00740 }
00741 
00742 
00743 
00744 /*
00745 =================
00746 ClientInactivityTimer
00747 
00748 Returns qfalse if the client is dropped
00749 =================
00750 */
00751 qboolean ClientInactivityTimer( gclient_t *client ) {
00752         if ( ! g_inactivity.integer ) {
00753                 // give everyone some time, so if the operator sets g_inactivity during
00754                 // gameplay, everyone isn't kicked
00755                 client->inactivityTime = level.time + 60 * 1000;
00756                 client->inactivityWarning = qfalse;
00757         } else if ( client->pers.cmd.forwardmove || 
00758                 client->pers.cmd.rightmove || 
00759                 client->pers.cmd.upmove ||
00760                 (client->pers.cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) {
00761                 client->inactivityTime = level.time + g_inactivity.integer * 1000;
00762                 client->inactivityWarning = qfalse;
00763         } else if ( !client->pers.localClient ) {
00764                 if ( level.time > client->inactivityTime ) {
00765                         trap_DropClient( client - level.clients, "Dropped due to inactivity" );
00766                         return qfalse;
00767                 }
00768                 if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) {
00769                         client->inactivityWarning = qtrue;
00770                         trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
00771                 }
00772         }
00773         return qtrue;
00774 }
00775 
00776 /*
00777 ==================
00778 ClientTimerActions
00779 
00780 Actions that happen once a second
00781 ==================
00782 */
00783 void ClientTimerActions( gentity_t *ent, int msec ) {
00784         gclient_t       *client;
00785 
00786         client = ent->client;
00787         client->timeResidual += msec;
00788 
00789         while ( client->timeResidual >= 1000 ) 
00790         {
00791                 client->timeResidual -= 1000;
00792 
00793                 // count down health when over max
00794                 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) {
00795                         ent->health--;
00796                 }
00797 
00798                 // count down armor when over max
00799                 if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) {
00800                         client->ps.stats[STAT_ARMOR]--;
00801                 }
00802         }
00803 }
00804 
00805 /*
00806 ====================
00807 ClientIntermissionThink
00808 ====================
00809 */
00810 void ClientIntermissionThink( gclient_t *client ) {
00811         client->ps.eFlags &= ~EF_TALK;
00812         client->ps.eFlags &= ~EF_FIRING;
00813 
00814         // the level will exit when everyone wants to or after timeouts
00815 
00816         // swap and latch button actions
00817         client->oldbuttons = client->buttons;
00818         client->buttons = client->pers.cmd.buttons;
00819         if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) {
00820                 // this used to be an ^1 but once a player says ready, it should stick
00821                 client->readyToExit = 1;
00822         }
00823 }
00824 
00825 extern void NPC_SetAnim(gentity_t       *ent,int setAnimParts,int anim,int setAnimFlags);
00826 void G_VehicleAttachDroidUnit( gentity_t *vehEnt )
00827 {
00828         if ( vehEnt && vehEnt->m_pVehicle && vehEnt->m_pVehicle->m_pDroidUnit != NULL )
00829         {
00830                 gentity_t *droidEnt = (gentity_t *)vehEnt->m_pVehicle->m_pDroidUnit;
00831                 mdxaBone_t boltMatrix;
00832                 vec3_t  fwd;
00833 
00834                 trap_G2API_GetBoltMatrix(vehEnt->ghoul2, 0, vehEnt->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehEnt->r.currentAngles, vehEnt->r.currentOrigin, level.time,
00835                         NULL, vehEnt->modelScale);
00836                 BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidEnt->r.currentOrigin);
00837                 BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fwd);
00838                 vectoangles( fwd, droidEnt->r.currentAngles );
00839                 
00840                 if ( droidEnt->client )
00841                 {
00842                         VectorCopy( droidEnt->r.currentAngles, droidEnt->client->ps.viewangles );
00843                         VectorCopy( droidEnt->r.currentOrigin, droidEnt->client->ps.origin );
00844                 }
00845 
00846                 G_SetOrigin( droidEnt, droidEnt->r.currentOrigin );
00847                 trap_LinkEntity( droidEnt );
00848                 
00849                 if ( droidEnt->NPC )
00850                 {
00851                         NPC_SetAnim( droidEnt, SETANIM_BOTH, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00852                 }
00853         }
00854 }
00855 
00856 //called gameside only from pmove code (convenience)
00857 void G_CheapWeaponFire(int entNum, int ev)
00858 {
00859         gentity_t *ent = &g_entities[entNum];
00860         
00861         if (!ent->inuse || !ent->client)
00862         {
00863                 return;
00864         }
00865 
00866         switch (ev)
00867         {
00868                 case EV_FIRE_WEAPON:
00869                         if (ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER &&
00870                                 ent->client && ent->client->ps.m_iVehicleNum)
00871                         { //a speeder with a pilot
00872                                 gentity_t *rider = &g_entities[ent->client->ps.m_iVehicleNum-1];
00873                                 if (rider->inuse && rider->client)
00874                                 { //pilot is valid...
00875                     if (rider->client->ps.weapon != WP_MELEE &&
00876                                                 (rider->client->ps.weapon != WP_SABER || !rider->client->ps.saberHolstered))
00877                                         { //can only attack on speeder when using melee or when saber is holstered
00878                                                 break;
00879                                         }
00880                                 }
00881                         }
00882 
00883                         FireWeapon( ent, qfalse );
00884                         ent->client->dangerTime = level.time;
00885                         ent->client->ps.eFlags &= ~EF_INVULNERABLE;
00886                         ent->client->invulnerableTimer = 0;
00887                         break;
00888                 case EV_ALT_FIRE:
00889                         FireWeapon( ent, qtrue );
00890                         ent->client->dangerTime = level.time;
00891                         ent->client->ps.eFlags &= ~EF_INVULNERABLE;
00892                         ent->client->invulnerableTimer = 0;
00893                         break;
00894         }
00895 }
00896 
00897 /*
00898 ================
00899 ClientEvents
00900 
00901 Events will be passed on to the clients for presentation,
00902 but any server game effects are handled here
00903 ================
00904 */
00905 #include "../namespace_begin.h"
00906 qboolean BG_InKnockDownOnly( int anim );
00907 #include "../namespace_end.h"
00908 
00909 void ClientEvents( gentity_t *ent, int oldEventSequence ) {
00910         int             i;//, j;
00911         int             event;
00912         gclient_t *client;
00913         int             damage;
00914         vec3_t  dir;
00915 //      vec3_t  origin, angles;
00916 //      qboolean        fired;
00917 //      gitem_t *item;
00918 //      gentity_t *drop;
00919 
00920         client = ent->client;
00921 
00922         if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) {
00923                 oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
00924         }
00925         for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) {
00926                 event = client->ps.events[ i & (MAX_PS_EVENTS-1) ];
00927 
00928                 switch ( event ) {
00929                 case EV_FALL:
00930                 case EV_ROLL:
00931                         {
00932                                 int delta = client->ps.eventParms[ i & (MAX_PS_EVENTS-1) ];
00933                                 qboolean knockDownage = qfalse;
00934 
00935                                 if (ent->client && ent->client->ps.fallingToDeath)
00936                                 {
00937                                         break;
00938                                 }
00939 
00940                                 if ( ent->s.eType != ET_PLAYER )
00941                                 {
00942                                         break;          // not in the player model
00943                                 }
00944                                 
00945                                 if ( g_dmflags.integer & DF_NO_FALLING )
00946                                 {
00947                                         break;
00948                                 }
00949 
00950                                 if (BG_InKnockDownOnly(ent->client->ps.legsAnim))
00951                                 {
00952                                         if (delta <= 14)
00953                                         {
00954                                                 break;
00955                                         }
00956                                         knockDownage = qtrue;
00957                                 }
00958                                 else
00959                                 {
00960                                         if (delta <= 44)
00961                                         {
00962                                                 break;
00963                                         }
00964                                 }
00965 
00966                                 if (knockDownage)
00967                                 {
00968                                         damage = delta*1; //you suffer for falling unprepared. A lot. Makes throws and things useful, and more realistic I suppose.
00969                                 }
00970                                 else
00971                                 {
00972                                         if (g_gametype.integer == GT_SIEGE &&
00973                                                 delta > 60)
00974                                         { //longer falls hurt more
00975                                                 damage = delta*1; //good enough for now, I guess
00976                                         }
00977                                         else
00978                                         {
00979                                                 damage = delta*0.16; //good enough for now, I guess
00980                                         }
00981                                 }
00982 
00983                                 VectorSet (dir, 0, 0, 1);
00984                                 ent->pain_debounce_time = level.time + 200;     // no normal pain sound
00985                                 G_Damage (ent, NULL, NULL, NULL, NULL, damage, DAMAGE_NO_ARMOR, MOD_FALLING);
00986 
00987                                 if (ent->health < 1)
00988                                 {
00989                                         G_Sound(ent, CHAN_AUTO, G_SoundIndex( "sound/player/fallsplat.wav" ));
00990                                 }
00991                         }
00992                         break;
00993                 case EV_FIRE_WEAPON:
00994                         FireWeapon( ent, qfalse );
00995                         ent->client->dangerTime = level.time;
00996                         ent->client->ps.eFlags &= ~EF_INVULNERABLE;
00997                         ent->client->invulnerableTimer = 0;
00998                         break;
00999 
01000                 case EV_ALT_FIRE:
01001                         FireWeapon( ent, qtrue );
01002                         ent->client->dangerTime = level.time;
01003                         ent->client->ps.eFlags &= ~EF_INVULNERABLE;
01004                         ent->client->invulnerableTimer = 0;
01005                         break;
01006 
01007                 case EV_SABER_ATTACK:
01008                         ent->client->dangerTime = level.time;
01009                         ent->client->ps.eFlags &= ~EF_INVULNERABLE;
01010                         ent->client->invulnerableTimer = 0;
01011                         break;
01012 
01013                 //rww - Note that these must be in the same order (ITEM#-wise) as they are in holdable_t
01014                 case EV_USE_ITEM1: //seeker droid
01015                         ItemUse_Seeker(ent);
01016                         break;
01017                 case EV_USE_ITEM2: //shield
01018                         ItemUse_Shield(ent);
01019                         break;
01020                 case EV_USE_ITEM3: //medpack
01021                         ItemUse_MedPack(ent);
01022                         break;
01023                 case EV_USE_ITEM4: //big medpack
01024                         ItemUse_MedPack_Big(ent);
01025                         break;
01026                 case EV_USE_ITEM5: //binoculars
01027                         ItemUse_Binoculars(ent);
01028                         break;
01029                 case EV_USE_ITEM6: //sentry gun
01030                         ItemUse_Sentry(ent);
01031                         break;
01032                 case EV_USE_ITEM7: //jetpack
01033                         ItemUse_Jetpack(ent);
01034                         break;
01035                 case EV_USE_ITEM8: //health disp
01036                         //ItemUse_UseDisp(ent, HI_HEALTHDISP);
01037                         break;
01038                 case EV_USE_ITEM9: //ammo disp
01039                         //ItemUse_UseDisp(ent, HI_AMMODISP);
01040                         break;
01041                 case EV_USE_ITEM10: //eweb
01042                         ItemUse_UseEWeb(ent);
01043                         break;
01044                 case EV_USE_ITEM11: //cloak
01045                         ItemUse_UseCloak(ent);
01046                         break;
01047                 default:
01048                         break;
01049                 }
01050         }
01051 
01052 }
01053 
01054 /*
01055 ==============
01056 SendPendingPredictableEvents
01057 ==============
01058 */
01059 void SendPendingPredictableEvents( playerState_t *ps ) {
01060         gentity_t *t;
01061         int event, seq;
01062         int extEvent, number;
01063 
01064         // if there are still events pending
01065         if ( ps->entityEventSequence < ps->eventSequence ) {
01066                 // create a temporary entity for this event which is sent to everyone
01067                 // except the client who generated the event
01068                 seq = ps->entityEventSequence & (MAX_PS_EVENTS-1);
01069                 event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
01070                 // set external event to zero before calling BG_PlayerStateToEntityState
01071                 extEvent = ps->externalEvent;
01072                 ps->externalEvent = 0;
01073                 // create temporary entity for event
01074                 t = G_TempEntity( ps->origin, event );
01075                 number = t->s.number;
01076                 BG_PlayerStateToEntityState( ps, &t->s, qtrue );
01077                 t->s.number = number;
01078                 t->s.eType = ET_EVENTS + event;
01079                 t->s.eFlags |= EF_PLAYER_EVENT;
01080                 t->s.otherEntityNum = ps->clientNum;
01081                 // send to everyone except the client who generated the event
01082                 t->r.svFlags |= SVF_NOTSINGLECLIENT;
01083                 t->r.singleClient = ps->clientNum;
01084                 // set back external event
01085                 ps->externalEvent = extEvent;
01086         }
01087 }
01088 
01089 /*
01090 ==================
01091 G_UpdateClientBroadcasts
01092 
01093 Determines whether this client should be broadcast to any other clients.  
01094 A client is broadcast when another client is using force sight or is
01095 ==================
01096 */
01097 #define MAX_JEDIMASTER_DISTANCE 2500
01098 #define MAX_JEDIMASTER_FOV              100
01099 
01100 #define MAX_SIGHT_DISTANCE              1500
01101 #define MAX_SIGHT_FOV                   100
01102 
01103 static void G_UpdateForceSightBroadcasts ( gentity_t *self )
01104 {
01105         int i;
01106 
01107         // Any clients with force sight on should see this client
01108         for ( i = 0; i < level.numConnectedClients; i ++ )
01109         {
01110                 gentity_t *ent = &g_entities[level.sortedClients[i]];
01111                 float     dist;
01112                 vec3_t    angles;
01113         
01114                 if ( ent == self )
01115                 {
01116                         continue;
01117                 }
01118 
01119                 // Not using force sight so we shouldnt broadcast to this one
01120                 if ( !(ent->client->ps.fd.forcePowersActive & (1<<FP_SEE) ) )
01121                 {
01122                         continue;
01123                 }
01124 
01125                 VectorSubtract( self->client->ps.origin, ent->client->ps.origin, angles );
01126                 dist = VectorLengthSquared ( angles );
01127                 vectoangles ( angles, angles );
01128 
01129                 // Too far away then just forget it
01130                 if ( dist > MAX_SIGHT_DISTANCE * MAX_SIGHT_DISTANCE )
01131                 {
01132                         continue;
01133                 }
01134                 
01135                 // If not within the field of view then forget it
01136                 if ( !InFieldOfVision ( ent->client->ps.viewangles, MAX_SIGHT_FOV, angles ) )
01137                 {
01138                         break;
01139                 }
01140 
01141                 // Turn on the broadcast bit for the master and since there is only one
01142                 // master we are done
01143                 self->r.broadcastClients[ent->s.clientNum/32] |= (1 << (ent->