/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ // cg_playerstate.c -- this file acts on changes in a new playerState_t // With normal play, this will be done after local prediction, but when // following another player or playing back a demo, it will be checked // when the snapshot transitions like all the other entities #include "cg_local.h" #include "cg_media.h" /* ============== CG_CheckAmmo If the ammo has gone low enough to generate the warning, play a sound ============== */ void CG_CheckAmmo( void ) { // int i; int total; int previous; // int weapons; #if 0 // see about how many seconds of ammo we have remaining weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; total = 0; for ( i = WP_STUN_BATON; i < WP_NUM_WEAPONS i++ ) { if ( ! ( weapons & ( 1 << i ) ) ) continue; /* switch ( i ) { case WP_ROCKET_LAUNCHER: case WP_GRENADE_LAUNCHER: case WP_RAILGUN: case WP_SHOTGUN: total += cg.snap->ps.ammo[i] * 1000; break; default: total += cg.snap->ps.ammo[i] * 200; break; } */ if ( total >= 5000 ) { cg.lowAmmoWarning = 0; return; } } #endif // Don't bother drawing the ammo warning when have no weapon selected if ( cg.weaponSelect == WP_NONE ) { return; } total = cg.snap->ps.ammo[weaponData[cg.weaponSelect].ammoIndex]; if (total > weaponData[cg.weaponSelect].ammoLow) // Low on ammo? { cg.lowAmmoWarning = 0; return; } previous = cg.lowAmmoWarning; if (!total) // We're completely freak'in out! { cg.lowAmmoWarning = 2; } else // Got a little left { cg.lowAmmoWarning = 1; } // play a sound on transitions if ( cg.lowAmmoWarning != previous ) { cgi_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); //"sound/weapons/noammo.wav" } } /* ============== CG_DamageFeedback ============== */ void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { float left, front, up; float kick; int health; float scale; vec3_t dir; vec3_t angles; float dist; float yaw, pitch; //FIXME: Based on MOD, do different kinds of damage effects, // for example, Borg damage could progressively tint screen green and raise FOV? // the lower on health you are, the greater the view kick will be health = cg.snap->ps.stats[STAT_HEALTH]; if ( health < 40 ) { scale = 1; } else { scale = 40.0 / health; } kick = damage * scale; if (kick < 5) kick = 5; if (kick > 10) kick = 10; // if yaw and pitch are both 255, make the damage always centered (falling, etc) if ( yawByte == 255 && pitchByte == 255 ) { cg.damageX = 0; cg.damageY = 0; cg.v_dmg_roll = 0; cg.v_dmg_pitch = -kick; } else { // positional pitch = pitchByte / 255.0 * 360; yaw = yawByte / 255.0 * 360; angles[PITCH] = pitch; angles[YAW] = yaw; angles[ROLL] = 0; AngleVectors( angles, dir, NULL, NULL ); VectorSubtract( vec3_origin, dir, dir ); front = DotProduct (dir, cg.refdef.viewaxis[0] ); left = DotProduct (dir, cg.refdef.viewaxis[1] ); up = DotProduct (dir, cg.refdef.viewaxis[2] ); dir[0] = front; dir[1] = left; dir[2] = 0; dist = VectorLength( dir ); if ( dist < 0.1 ) { dist = 0.1f; } cg.v_dmg_roll = kick * left; cg.v_dmg_pitch = -kick * front; if ( front <= 0.1 ) { front = 0.1f; } cg.damageX = -left / front; cg.damageY = up / dist; } // clamp the position if ( cg.damageX > 1.0 ) { cg.damageX = 1.0; } if ( cg.damageX < - 1.0 ) { cg.damageX = -1.0; } if ( cg.damageY > 1.0 ) { cg.damageY = 1.0; } if ( cg.damageY < - 1.0 ) { cg.damageY = -1.0; } // don't let the screen flashes vary as much if ( kick > 10 ) { kick = 10; } cg.damageValue = kick; cg.v_dmg_time = cg.time + DAMAGE_TIME; cg.damageTime = cg.snap->serverTime; } /* ================ CG_Respawn A respawn happened this snapshot ================ */ void CG_Respawn( void ) { // no error decay on player movement cg.thisFrameTeleport = qtrue; // display weapons available // cg.weaponSelectTime = cg.time; SetWeaponSelectTime(); // select the weapon the server says we are using cg.weaponSelect = cg.snap->ps.weapon; } /* ============== CG_CheckPlayerstateEvents ============== */ void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { int i; int event; centity_t *cent; #if 0 if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { cent = &cg_entities[ ps->clientNum ]; cent->currentState.event = ps->externalEvent; cent->currentState.eventParm = ps->externalEventParm; CG_EntityEvent( cent, cent->lerpOrigin ); } #endif for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { if ( ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)] || i >= ops->eventSequence ) { event = ps->events[ i & (MAX_PS_EVENTS-1) ]; cent = &cg_entities[ ps->clientNum ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; CG_EntityEvent( cent, cent->lerpOrigin ); } } } /* ================== CG_CheckLocalSounds ================== */ /* void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { const char *s; // hit changes if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { cgi_S_StartLocalSound( "sound/feedback/hit.wav" ); } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { cgi_S_StartLocalSound( "sound/feedback/hit_teammate.wav" ); } // score up / down changes if ( ps->persistant[PERS_SCORE] > ops->persistant[PERS_SCORE] ) { cgi_S_StartLocalSound( "sound/feedback/scoreup.wav" ); } else if ( ps->persistant[PERS_SCORE] < ops->persistant[PERS_SCORE] ) { cgi_S_StartLocalSound( "sound/feedback/scoredown.wav" ); } // reward sounds if ( ps->persistant[PERS_REWARD_COUNT] > ops->persistant[PERS_REWARD_COUNT] ) { switch ( ps->persistant[PERS_REWARD] ) { case REWARD_IMPRESSIVE: cgi_S_StartLocalSound( "sound/feedback/impressive.wav" ); break; case REWARD_EXCELLENT: cgi_S_StartLocalSound( "sound/feedback/excellent.wav" ); break; case REWARD_DENIED: cgi_S_StartLocalSound( "sound/feedback/denied.wav" ); break; default: CG_Error( "Bad reward_t" ); } } // timelimit warnings if ( cgs.timelimit > 0 ) { if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && cg.time > (cgs.timelimit - 5) * 60 * 1000 ) { cg.timelimitWarnings |= 1; cgi_S_StartLocalSound( "sound/feedback/5_minute.wav" ); } if ( !( cg.timelimitWarnings & 2 ) && cg.time > (cgs.timelimit - 1) * 60 * 1000 ) { cg.timelimitWarnings |= 2; cgi_S_StartLocalSound( "sound/feedback/1_minute.wav" ); } if ( !( cg.timelimitWarnings & 4 ) && cg.time > ( cgs.timelimit * 60 + 2 ) * 1000 ) { cg.timelimitWarnings |= 4; cgi_S_StartLocalSound( "sound/feedback/sudden_death.wav" ); } } } */ /* =============== CG_TransitionPlayerState =============== */ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { // teleporting if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { cg.thisFrameTeleport = qtrue; } else { cg.thisFrameTeleport = qfalse; } // check for changing follow mode if ( ps->clientNum != ops->clientNum ) { cg.thisFrameTeleport = qtrue; // make sure we don't get any unwanted transition effects *ops = *ps; } // damage events (player is getting wounded) if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); } // respawning if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { CG_Respawn(); } // check for going low on ammo CG_CheckAmmo(); // run events CG_CheckPlayerstateEvents( ps, ops ); // smooth the ducking viewheight change if ( ps->viewheight != ops->viewheight ) { if ( !cg.nextFrameTeleport ) {//when we crouch/uncrouch in mid-air, our viewhieght doesn't actually change in //absolute world coordinates, just locally. cg.duckChange = ps->viewheight - ops->viewheight; cg.duckTime = cg.time; } } }