codemp/cgame/cg_playerstate.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 // cg_playerstate.c -- this file acts on changes in a new playerState_t
00004 // With normal play, this will be done after local prediction, but when
00005 // following another player or playing back a demo, it will be checked
00006 // when the snapshot transitions like all the other entities
00007 
00008 #include "cg_local.h"
00009 
00010 /*
00011 ==============
00012 CG_CheckAmmo
00013 
00014 If the ammo has gone low enough to generate the warning, play a sound
00015 ==============
00016 */
00017 void CG_CheckAmmo( void ) {
00018 #if 0
00019         int             i;
00020         int             total;
00021         int             previous;
00022         int             weapons;
00023 
00024         // see about how many seconds of ammo we have remaining
00025         weapons = cg.snap->ps.stats[ STAT_WEAPONS ];
00026         total = 0;
00027         for ( i = WP_BRYAR_PISTOL; i < WP_NUM_WEAPONS ; i++ ) {
00028                 if ( ! ( weapons & ( 1 << i ) ) ) {
00029                         continue;
00030                 }
00031                 switch ( i ) 
00032                 {
00033                 case WP_BRYAR_PISTOL:
00034                 case WP_CONCUSSION:
00035                 case WP_BRYAR_OLD:
00036                 case WP_BLASTER:
00037                 case WP_DISRUPTOR:
00038                 case WP_BOWCASTER:
00039                 case WP_REPEATER:
00040                 case WP_DEMP2:
00041                 case WP_FLECHETTE:
00042                 case WP_ROCKET_LAUNCHER:
00043                 case WP_THERMAL:
00044                 case WP_TRIP_MINE:
00045                 case WP_DET_PACK:
00046                 case WP_EMPLACED_GUN:
00047                         total += cg.snap->ps.ammo[weaponData[i].ammoIndex] * 1000;
00048                         break;
00049                 default:
00050                         total += cg.snap->ps.ammo[weaponData[i].ammoIndex] * 200;
00051                         break;
00052                 }
00053                 if ( total >= 5000 ) {
00054                         cg.lowAmmoWarning = 0;
00055                         return;
00056                 }
00057         }
00058 
00059         previous = cg.lowAmmoWarning;
00060 
00061         if ( total == 0 ) {
00062                 cg.lowAmmoWarning = 2;
00063         } else {
00064                 cg.lowAmmoWarning = 1;
00065         }
00066 
00067         if (cg.snap->ps.weapon == WP_SABER)
00068         {
00069                 cg.lowAmmoWarning = 0;
00070         }
00071 
00072         // play a sound on transitions
00073         if ( cg.lowAmmoWarning != previous ) {
00074                 trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND );
00075         }
00076 #endif
00077         //disabled silly ammo warning stuff for now
00078 }
00079 
00080 /*
00081 ==============
00082 CG_DamageFeedback
00083 ==============
00084 */
00085 void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) {
00086         float           left, front, up;
00087         float           kick;
00088         int                     health;
00089         float           scale;
00090         vec3_t          dir;
00091         vec3_t          angles;
00092         float           dist;
00093         float           yaw, pitch;
00094 
00095         // show the attacking player's head and name in corner
00096         cg.attackerTime = cg.time;
00097 
00098         // the lower on health you are, the greater the view kick will be
00099         health = cg.snap->ps.stats[STAT_HEALTH];
00100         if ( health < 40 ) {
00101                 scale = 1;
00102         } else {
00103                 scale = 40.0 / health;
00104         }
00105         kick = damage * scale;
00106 
00107         if (kick < 5)
00108                 kick = 5;
00109         if (kick > 10)
00110                 kick = 10;
00111 
00112         // if yaw and pitch are both 255, make the damage always centered (falling, etc)
00113         if ( yawByte == 255 && pitchByte == 255 ) {
00114                 cg.damageX = 0;
00115                 cg.damageY = 0;
00116                 cg.v_dmg_roll = 0;
00117                 cg.v_dmg_pitch = -kick;
00118         } else {
00119                 // positional
00120                 pitch = pitchByte / 255.0 * 360;
00121                 yaw = yawByte / 255.0 * 360;
00122 
00123                 angles[PITCH] = pitch;
00124                 angles[YAW] = yaw;
00125                 angles[ROLL] = 0;
00126 
00127                 AngleVectors( angles, dir, NULL, NULL );
00128                 VectorSubtract( vec3_origin, dir, dir );
00129 
00130                 front = DotProduct (dir, cg.refdef.viewaxis[0] );
00131                 left = DotProduct (dir, cg.refdef.viewaxis[1] );
00132                 up = DotProduct (dir, cg.refdef.viewaxis[2] );
00133 
00134                 dir[0] = front;
00135                 dir[1] = left;
00136                 dir[2] = 0;
00137                 dist = VectorLength( dir );
00138                 if ( dist < 0.1 ) {
00139                         dist = 0.1f;
00140                 }
00141 
00142                 cg.v_dmg_roll = kick * left;
00143                 
00144                 cg.v_dmg_pitch = -kick * front;
00145 
00146                 if ( front <= 0.1 ) {
00147                         front = 0.1f;
00148                 }
00149                 cg.damageX = -left / front;
00150                 cg.damageY = up / dist;
00151         }
00152 
00153         // clamp the position
00154         if ( cg.damageX > 1.0 ) {
00155                 cg.damageX = 1.0;
00156         }
00157         if ( cg.damageX < - 1.0 ) {
00158                 cg.damageX = -1.0;
00159         }
00160 
00161         if ( cg.damageY > 1.0 ) {
00162                 cg.damageY = 1.0;
00163         }
00164         if ( cg.damageY < - 1.0 ) {
00165                 cg.damageY = -1.0;
00166         }
00167 
00168         // don't let the screen flashes vary as much
00169         if ( kick > 10 ) {
00170                 kick = 10;
00171         }
00172         cg.damageValue = kick;
00173         cg.v_dmg_time = cg.time + DAMAGE_TIME;
00174         cg.damageTime = cg.snap->serverTime;
00175 
00176 //JLFRUMBLE
00177 #ifdef _XBOX
00178 extern void FF_XboxShake(float intensity, int duration);
00179 extern void FF_XboxDamage(int damage, float xpos);
00180 
00181 //FF_XboxShake(kick, 500);
00182 FF_XboxDamage(damage, -left);
00183 
00184 
00185 #endif
00186 
00187 }
00188 
00189 
00190 
00191 
00192 /*
00193 ================
00194 CG_Respawn
00195 
00196 A respawn happened this snapshot
00197 ================
00198 */
00199 void CG_Respawn( void ) {
00200         // no error decay on player movement
00201         cg.thisFrameTeleport = qtrue;
00202 
00203         // display weapons available
00204         cg.weaponSelectTime = cg.time;
00205 
00206         // select the weapon the server says we are using
00207         cg.weaponSelect = cg.snap->ps.weapon;
00208 }
00209 
00210 extern char *eventnames[];
00211 
00212 /*
00213 ==============
00214 CG_CheckPlayerstateEvents
00215 ==============
00216 */
00217 void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) {
00218         int                     i;
00219         int                     event;
00220         centity_t       *cent;
00221 
00222         if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) {
00223                 cent = &cg_entities[ ps->clientNum ];
00224                 cent->currentState.event = ps->externalEvent;
00225                 cent->currentState.eventParm = ps->externalEventParm;
00226                 CG_EntityEvent( cent, cent->lerpOrigin );
00227         }
00228 
00229         cent = &cg_entities[ ps->clientNum ];
00230         // go through the predictable events buffer
00231         for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) {
00232                 // if we have a new predictable event
00233                 if ( i >= ops->eventSequence
00234                         // or the server told us to play another event instead of a predicted event we already issued
00235                         // or something the server told us changed our prediction causing a different event
00236                         || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) {
00237 
00238                         event = ps->events[ i & (MAX_PS_EVENTS-1) ];
00239                         cent->currentState.event = event;
00240                         cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ];
00241 //JLF ADDED to hopefully mark events as player event
00242                         cent->playerState = ps;
00243                         CG_EntityEvent( cent, cent->lerpOrigin );
00244 
00245                         cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event;
00246 
00247                         cg.eventSequence++;
00248                 }
00249         }
00250 }
00251 
00252 /*
00253 ==================
00254 CG_CheckChangedPredictableEvents
00255 ==================
00256 */
00257 void CG_CheckChangedPredictableEvents( playerState_t *ps ) {
00258         int i;
00259         int event;
00260         centity_t       *cent;
00261 
00262         cent = &cg_entities[ps->clientNum];
00263         for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) {
00264                 //
00265                 if (i >= cg.eventSequence) {
00266                         continue;
00267                 }
00268                 // if this event is not further back in than the maximum predictable events we remember
00269                 if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) {
00270                         // if the new playerstate event is different from a previously predicted one
00271                         if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) {
00272 
00273                                 event = ps->events[ i & (MAX_PS_EVENTS-1) ];
00274                                 cent->currentState.event = event;
00275                                 cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ];
00276                                 CG_EntityEvent( cent, cent->lerpOrigin );
00277 
00278                                 cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event;
00279 
00280                                 if ( cg_showmiss.integer ) {
00281                                         CG_Printf("WARNING: changed predicted event\n");
00282                                 }
00283                         }
00284                 }
00285         }
00286 }
00287 
00288 /*
00289 ==================
00290 pushReward
00291 ==================
00292 */
00293 #ifdef JK2AWARDS
00294 static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) {
00295         if (cg.rewardStack < (MAX_REWARDSTACK-1)) {
00296                 cg.rewardStack++;
00297                 cg.rewardSound[cg.rewardStack] = sfx;
00298                 cg.rewardShader[cg.rewardStack] = shader;
00299                 cg.rewardCount[cg.rewardStack] = rewardCount;
00300         }
00301 }
00302 #endif
00303 
00304 int cgAnnouncerTime = 0; //to prevent announce sounds from playing on top of each other
00305 
00306 /*
00307 ==================
00308 CG_CheckLocalSounds
00309 ==================
00310 */
00311 void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) {
00312         int                     highScore, health, armor, reward;
00313 #ifdef JK2AWARDS
00314         sfxHandle_t sfx;
00315 #endif
00316 
00317         // don't play the sounds if the player just changed teams
00318         if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) {
00319                 return;
00320         }
00321 
00322         // hit changes
00323         if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) {
00324                 armor  = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff;
00325                 health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8;
00326 
00327                 if (armor > health/2)
00328                 {       // We also hit shields along the way, so consider them "pierced".
00329 //                      trap_S_StartLocalSound( cgs.media.shieldPierceSound, CHAN_LOCAL_SOUND );
00330                 }
00331                 else
00332                 {       // Shields didn't really stand in our way.
00333 //                      trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND );
00334                 }
00335 
00336                 //FIXME: Hit sounds?
00337                 /*
00338                 if (armor > 50 ) {
00339                         trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND );
00340                 } else if (armor || health > 100) {
00341                         trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND );
00342                 } else {
00343                         trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND );
00344                 }
00345                 */
00346         } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) {
00347                 //trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND );
00348         }
00349 
00350         // health changes of more than -3 should make pain sounds
00351         if (cg_oldPainSounds.integer)
00352         {
00353                 if ( ps->stats[STAT_HEALTH] < (ops->stats[STAT_HEALTH] - 3))
00354                 {
00355                         if ( ps->stats[STAT_HEALTH] > 0 )
00356                         {
00357                                 CG_PainEvent( &cg_entities[cg.predictedPlayerState.clientNum], ps->stats[STAT_HEALTH] );
00358                         }
00359                 }
00360         }
00361 
00362         // if we are going into the intermission, don't start any voices
00363         if ( cg.intermissionStarted ) {
00364                 return;
00365         }
00366 
00367 #ifdef JK2AWARDS
00368         // reward sounds
00369         reward = qfalse;
00370         if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) {
00371                 pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]);
00372                 reward = qtrue;
00373                 //Com_Printf("capture\n");
00374         }
00375         if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) {
00376                 sfx = cgs.media.impressiveSound;
00377 
00378                 pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]);
00379                 reward = qtrue;
00380                 //Com_Printf("impressive\n");
00381         }
00382         if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) {
00383                 sfx = cgs.media.excellentSound;
00384                 pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]);
00385                 reward = qtrue;
00386                 //Com_Printf("excellent\n");
00387         }
00388         if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) {
00389                 sfx = cgs.media.humiliationSound;
00390                 pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]);
00391                 reward = qtrue;
00392                 //Com_Printf("guantlet frag\n");
00393         }
00394         if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) {
00395                 pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]);
00396                 reward = qtrue;
00397                 //Com_Printf("defend\n");
00398         }
00399         if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) {
00400                 //pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]);
00401                 //reward = qtrue;
00402                 //Com_Printf("assist\n");
00403         }
00404         // if any of the player event bits changed
00405         if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) {
00406                 if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) !=
00407                                 (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) {
00408                         trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER );
00409                 }
00410                 else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) !=
00411                                 (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) {
00412                         trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER );
00413                 }
00414                 reward = qtrue;
00415         }
00416 #else
00417         reward = qfalse;
00418 #endif
00419         // lead changes
00420         if (!reward && cgAnnouncerTime < cg.time) {
00421                 //
00422                 if ( !cg.warmup && cgs.gametype != GT_POWERDUEL ) {
00423                         // never play lead changes during warmup and powerduel
00424                         if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) {
00425                                 if ( cgs.gametype < GT_TEAM) {
00426                                         /*
00427                                         if (  ps->persistant[PERS_RANK] == 0 ) {
00428                                                 CG_AddBufferedSound(cgs.media.takenLeadSound);
00429                                                 cgAnnouncerTime = cg.time + 3000;
00430                                         } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) {
00431                                                 //CG_AddBufferedSound(cgs.media.tiedLeadSound);
00432                                         } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) {
00433                                                 //rww - only bother saying this if you have more than 1 kill already.
00434                                                 //joining the server and hearing "the force is not with you" is silly.
00435                                                 if (ps->persistant[PERS_SCORE] > 0)
00436                                                 {
00437                                                         CG_AddBufferedSound(cgs.media.lostLeadSound);
00438                                                         cgAnnouncerTime = cg.time + 3000;
00439                                                 }
00440                                         }
00441                                         */
00442                                 }
00443                         }
00444                 }
00445         }
00446 
00447         // timelimit warnings
00448         if ( cgs.timelimit > 0 && cgAnnouncerTime < cg.time ) {
00449                 int             msec;
00450 
00451                 msec = cg.time - cgs.levelStartTime;
00452                 if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) {
00453                         cg.timelimitWarnings |= 1 | 2 | 4;
00454                         //trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER );
00455                 }
00456                 else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) {
00457                         cg.timelimitWarnings |= 1 | 2;
00458                         trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER );
00459                         cgAnnouncerTime = cg.time + 3000;
00460                 }
00461                 else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) {
00462                         cg.timelimitWarnings |= 1;
00463                         trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER );
00464                         cgAnnouncerTime = cg.time + 3000;
00465                 }
00466         }
00467 
00468         // fraglimit warnings
00469         if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF && cgs.gametype != GT_DUEL && cgs.gametype != GT_POWERDUEL && cgs.gametype != GT_SIEGE && cgAnnouncerTime < cg.time) {
00470                 highScore = cgs.scores1;
00471                 if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) {
00472                         cg.fraglimitWarnings |= 1 | 2 | 4;
00473                         CG_AddBufferedSound(cgs.media.oneFragSound);
00474                         cgAnnouncerTime = cg.time + 3000;
00475                 }
00476                 else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) {
00477                         cg.fraglimitWarnings |= 1 | 2;
00478                         CG_AddBufferedSound(cgs.media.twoFragSound);
00479                         cgAnnouncerTime = cg.time + 3000;
00480                 }
00481                 else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) {
00482                         cg.fraglimitWarnings |= 1;
00483                         CG_AddBufferedSound(cgs.media.threeFragSound);
00484                         cgAnnouncerTime = cg.time + 3000;
00485                 }
00486         }
00487 }
00488 
00489 /*
00490 ===============
00491 CG_TransitionPlayerState
00492 
00493 ===============
00494 */
00495 void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) {
00496         // check for changing follow mode
00497         if ( ps->clientNum != ops->clientNum ) {
00498                 cg.thisFrameTeleport = qtrue;
00499                 // make sure we don't get any unwanted transition effects
00500                 *ops = *ps;
00501         }
00502 
00503         // damage events (player is getting wounded)
00504         if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) {
00505                 CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount );
00506         }
00507 
00508         // respawning
00509         if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) {
00510                 CG_Respawn();
00511         }
00512 
00513         if ( cg.mapRestart ) {
00514                 CG_Respawn();
00515                 cg.mapRestart = qfalse;
00516         }
00517 
00518         if ( cg.snap->ps.pm_type != PM_INTERMISSION 
00519                 && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) {
00520                 CG_CheckLocalSounds( ps, ops );
00521         }
00522 
00523         // check for going low on ammo
00524         CG_CheckAmmo();
00525 
00526         // run events
00527         CG_CheckPlayerstateEvents( ps, ops );
00528 
00529         // smooth the ducking viewheight change
00530         if ( ps->viewheight != ops->viewheight ) {
00531                 cg.duckChange = ps->viewheight - ops->viewheight;
00532                 cg.duckTime = cg.time;
00533         }
00534 }
00535