/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Wolf ET Source Code 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // 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" /* ============== 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 weapons[MAX_WEAPONS / ( sizeof( int ) * 8 )]; // see about how many seconds of ammo we have remaining memcpy( weapons, cg.snap->ps.weapons, sizeof( weapons ) ); if ( !weapons[0] && !weapons[1] ) { // (SA) we start out with no weapons, so don't make a click on startup return; } total = 0; for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { if ( !( weapons[0] & ( 1 << i ) ) ) { continue; } switch ( i ) { case WP_PANZERFAUST: case WP_GRENADE_LAUNCHER: case WP_GRENADE_PINEAPPLE: case WP_LUGER: case WP_COLT: case WP_AKIMBO_COLT: case WP_AKIMBO_SILENCEDCOLT: case WP_AKIMBO_LUGER: case WP_AKIMBO_SILENCEDLUGER: case WP_SILENCER: case WP_MP40: case WP_THOMPSON: case WP_STEN: case WP_GARAND: case WP_FG42: case WP_FG42SCOPE: case WP_KAR98: case WP_GPG40: case WP_CARBINE: case WP_M7: case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: case WP_K43: case WP_MORTAR_SET: default: total += cg.snap->ps.ammo[BG_FindAmmoForWeapon( i )] * 1000; break; // default: // total += cg.snap->ps.ammo[BG_FindAmmoForWeapon(i)] * 200; // break; } if ( total >= 5000 ) { cg.lowAmmoWarning = 0; return; } } if ( !cg.lowAmmoWarning ) { // play a sound on this transition trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); } if ( total == 0 ) { cg.lowAmmoWarning = 2; } else { cg.lowAmmoWarning = 1; } } /* ============== 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; int slot; viewDamage_t *vd; // show the attacking player's head and name in corner cg.attackerTime = cg.time; // 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; } // find a free slot for ( slot = 0; slot < MAX_VIEWDAMAGE; slot++ ) { if ( cg.viewDamage[slot].damageTime + cg.viewDamage[slot].damageDuration < cg.time ) { break; } } if ( slot == MAX_VIEWDAMAGE ) { return; // no free slots, never override or splats will suddenly disappear } vd = &cg.viewDamage[slot]; // if yaw and pitch are both 255, make the damage always centered (falling, etc) if ( yawByte == 255 && pitchByte == 255 ) { vd->damageX = 0; vd->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.1; } cg.v_dmg_roll = kick * left; cg.v_dmg_pitch = -kick * front; if ( front <= 0.1 ) { front = 0.1; } vd->damageX = crandom() * 0.3 + - left / front; vd->damageY = crandom() * 0.3 + up / dist; } // clamp the position if ( vd->damageX > 1.0 ) { vd->damageX = 1.0; } if ( vd->damageX < -1.0 ) { vd->damageX = -1.0; } if ( vd->damageY > 1.0 ) { vd->damageY = 1.0; } if ( vd->damageY < -1.0 ) { vd->damageY = -1.0; } // don't let the screen flashes vary as much if ( kick > 10 ) { kick = 10; } vd->damageValue = kick; cg.v_dmg_time = cg.time + DAMAGE_TIME; vd->damageTime = cg.snap->serverTime; vd->damageDuration = kick * 50 * ( 1 + 2 * ( !vd->damageX && !vd->damageY ) ); cg.damageTime = cg.snap->serverTime; cg.damageIndex = slot; } /* ================ CG_Respawn A respawn happened this snapshot ================ */ void CG_Respawn( qboolean revived ) { cg.serverRespawning = qfalse; // Arnout: just in case // no error decay on player movement cg.thisFrameTeleport = qtrue; // need to reset client-side weapon animations cg.predictedPlayerState.weapAnim = ( ( cg.predictedPlayerState.weapAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | PM_IdleAnimForWeapon( cg.snap->ps.weapon ); // reset weapon animations cg.predictedPlayerState.weaponstate = WEAPON_READY; // hmm, set this? what to? // display weapons available cg.weaponSelectTime = cg.time; cg.cursorHintIcon = 0; cg.cursorHintTime = 0; cg.cameraMode = qfalse; //----(SA) get out of camera for sure // select the weapon the server says we are using cg.weaponSelect = cg.snap->ps.weapon; // DHM - Nerve :: Clear even more things on respawn cg.zoomedBinoc = qfalse; cg.zoomedScope = qfalse; cg.zoomTime = 0; cg.zoomval = 0; trap_SendConsoleCommand( "-zoom\n" ); cg.binocZoomTime = 0; // clear pmext memset( &cg.pmext, 0, sizeof( cg.pmext ) ); cg.pmext.bAutoReload = ( cg_autoReload.integer > 0 ); cg.pmext.sprintTime = SPRINTTIME; if ( !revived ) { cgs.limboLoadoutSelected = qfalse; } if ( cg.predictedPlayerState.stats[STAT_PLAYER_CLASS] == PC_COVERTOPS ) { cg.pmext.silencedSideArm = 1; } cg.proneMovingTime = 0; // reset fog to world fog (if present) trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP,20,0,0,0,0 ); // dhm - end } extern char *eventnames[]; /* ============== CG_CheckPlayerstateEvents ============== */ void CG_CheckPlayerstateEvents_wolf( playerState_t *ps, playerState_t *ops ) { int i; int event; centity_t *cent; /* 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 ); } */ cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; // go through the predictable events buffer for ( i = ps->eventSequence - MAX_EVENTS ; i < ps->eventSequence ; i++ ) { if ( ps->events[i & ( MAX_EVENTS - 1 )] != ops->events[i & ( MAX_EVENTS - 1 )] || i >= ops->eventSequence ) { event = ps->events[ i & ( MAX_EVENTS - 1 ) ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & ( MAX_EVENTS - 1 ) ]; CG_EntityEvent( cent, cent->lerpOrigin ); } } } void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { int i; int event; centity_t *cent; 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 ); } cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; // go through the predictable events buffer for ( i = ps->eventSequence - MAX_EVENTS ; i < ps->eventSequence ; i++ ) { // if we have a new predictable event if ( i >= ops->eventSequence // or the server told us to play another event instead of a predicted event we already issued // or something the server told us changed our prediction causing a different event || ( i > ops->eventSequence - MAX_EVENTS && ps->events[i & ( MAX_EVENTS - 1 )] != ops->events[i & ( MAX_EVENTS - 1 )] ) ) { event = ps->events[ i & ( MAX_EVENTS - 1 ) ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & ( MAX_EVENTS - 1 ) ]; CG_EntityEvent( cent, cent->lerpOrigin ); cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; cg.eventSequence++; } } } /* ================== CG_CheckChangedPredictableEvents ================== */ void CG_CheckChangedPredictableEvents( playerState_t *ps ) { int i; int event; centity_t *cent; cent = &cg.predictedPlayerEntity; for ( i = ps->eventSequence - MAX_EVENTS ; i < ps->eventSequence ; i++ ) { // if ( i >= cg.eventSequence ) { continue; } // if this event is not further back in than the maximum predictable events we remember if ( i > cg.eventSequence - MAX_PREDICTED_EVENTS ) { // if the new playerstate event is different from a previously predicted one if ( ps->events[i & ( MAX_EVENTS - 1 )] != cg.predictableEvents[i & ( MAX_PREDICTED_EVENTS - 1 ) ] ) { event = ps->events[ i & ( MAX_EVENTS - 1 ) ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & ( MAX_EVENTS - 1 ) ]; CG_EntityEvent( cent, cent->lerpOrigin ); cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; if ( cg_showmiss.integer ) { CG_Printf( "WARNING: changed predicted event\n" ); } } } } } /* ================== CG_CheckLocalSounds ================== */ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { // health changes of more than -1 should make pain sounds if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { if ( ps->stats[STAT_HEALTH] > 0 ) { CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH], qfalse ); cg.painTime = cg.time; } } // timelimit warnings if ( cgs.timelimit > 0 && cgs.gamestate == GS_PLAYING ) { int msec; msec = cg.time - cgs.levelStartTime; if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && ( msec > ( cgs.timelimit - 5 ) * 60 * 1000 ) && ( msec < ( cgs.timelimit - 5 ) * 60 * 1000 + 1000 ) ) { cg.timelimitWarnings |= 1; if ( ps->persistant[PERS_TEAM] == TEAM_AXIS ) { if ( cgs.media.fiveMinuteSound_g == -1 ) { CG_SoundPlaySoundScript( cg.fiveMinuteSound_g, NULL, -1, qtrue ); } else if ( cgs.media.fiveMinuteSound_g ) { trap_S_StartLocalSound( cgs.media.fiveMinuteSound_g, CHAN_ANNOUNCER ); } } else if ( ps->persistant[PERS_TEAM] == TEAM_ALLIES ) { if ( cgs.media.fiveMinuteSound_a == -1 ) { CG_SoundPlaySoundScript( cg.fiveMinuteSound_a, NULL, -1, qtrue ); } else if ( cgs.media.fiveMinuteSound_a ) { trap_S_StartLocalSound( cgs.media.fiveMinuteSound_a, CHAN_ANNOUNCER ); } } } if ( cgs.timelimit > 2 && !( cg.timelimitWarnings & 2 ) && ( msec > ( cgs.timelimit - 2 ) * 60 * 1000 ) && ( msec < ( cgs.timelimit - 2 ) * 60 * 1000 + 1000 ) ) { cg.timelimitWarnings |= 2; if ( ps->persistant[PERS_TEAM] == TEAM_AXIS ) { if ( cgs.media.twoMinuteSound_g == -1 ) { CG_SoundPlaySoundScript( cg.twoMinuteSound_g, NULL, -1, qtrue ); } else if ( cgs.media.twoMinuteSound_g ) { trap_S_StartLocalSound( cgs.media.twoMinuteSound_g, CHAN_ANNOUNCER ); } } else if ( ps->persistant[PERS_TEAM] == TEAM_ALLIES ) { if ( cgs.media.twoMinuteSound_a == -1 ) { CG_SoundPlaySoundScript( cg.twoMinuteSound_a, NULL, -1, qtrue ); } else if ( cgs.media.twoMinuteSound_a ) { trap_S_StartLocalSound( cgs.media.twoMinuteSound_a, CHAN_ANNOUNCER ); } } } if ( !( cg.timelimitWarnings & 4 ) && ( msec > ( cgs.timelimit ) * 60 * 1000 - 30000 ) && ( msec < ( cgs.timelimit ) * 60 * 1000 - 29000 ) ) { cg.timelimitWarnings |= 4; if ( ps->persistant[PERS_TEAM] == TEAM_AXIS ) { if ( cgs.media.thirtySecondSound_g == -1 ) { CG_SoundPlaySoundScript( cg.thirtySecondSound_g, NULL, -1, qtrue ); } else if ( cgs.media.thirtySecondSound_g ) { trap_S_StartLocalSound( cgs.media.thirtySecondSound_g, CHAN_ANNOUNCER ); } } else if ( ps->persistant[PERS_TEAM] == TEAM_ALLIES ) { if ( cgs.media.thirtySecondSound_a == -1 ) { CG_SoundPlaySoundScript( cg.thirtySecondSound_a, NULL, -1, qtrue ); } else if ( cgs.media.thirtySecondSound_a ) { trap_S_StartLocalSound( cgs.media.thirtySecondSound_a, CHAN_ANNOUNCER ); } } } } } /* =============== CG_TransitionPlayerState =============== */ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { // OSP - MV client handling if ( cg.mvTotalClients > 0 ) { if ( ps->clientNum != ops->clientNum ) { cg.thisFrameTeleport = qtrue; // clear voicechat cg.predictedPlayerEntity.voiceChatSpriteTime = 0; // CHECKME: should we do this here? cg_entities[ps->clientNum].voiceChatSpriteTime = 0; *ops = *ps; } CG_CheckLocalSounds( ps, ops ); return; } // check for changing follow mode if ( ps->clientNum != ops->clientNum ) { cg.thisFrameTeleport = qtrue; // clear voicechat cg.predictedPlayerEntity.voiceChatSpriteTime = 0; cg_entities[ps->clientNum].voiceChatSpriteTime = 0; // make sure we don't get any unwanted transition effects *ops = *ps; // DHM - Nerve :: After Limbo, make sure and do a CG_Respawn if ( ps->clientNum == cg.clientNum ) { ops->persistant[PERS_SPAWN_COUNT]--; } } if ( ps->eFlags & EF_FIRING ) { cg.lastFiredWeaponTime = 0; cg.weaponFireTime += cg.frametime; } else { if ( cg.weaponFireTime > 500 && cg.weaponFireTime ) { cg.lastFiredWeaponTime = cg.time; } cg.weaponFireTime = 0; } // 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( ps->persistant[PERS_REVIVE_COUNT] != ops->persistant[PERS_REVIVE_COUNT] ? qtrue : qfalse ); } if ( cg.mapRestart ) { CG_Respawn( qfalse ); cg.mapRestart = qfalse; } if ( cg.snap->ps.pm_type != PM_INTERMISSION && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { CG_CheckLocalSounds( ps, ops ); } // check for going low on ammo CG_CheckAmmo(); if ( ps->eFlags & EF_PRONE_MOVING ) { if ( ps->weapon == WP_BINOCULARS ) { if ( ps->eFlags & EF_ZOOMING ) { trap_SendConsoleCommand( "-zoom\n" ); } } if ( !( ops->eFlags & EF_PRONE_MOVING ) ) { // ydnar: this screws up auto-switching when dynamite planted or grenade thrown/out of ammo //% CG_FinishWeaponChange( cg.weaponSelect, ps->nextWeapon ); cg.proneMovingTime = cg.time; } } else if ( ops->eFlags & EF_PRONE_MOVING ) { cg.proneMovingTime = -cg.time; } if ( !( ps->eFlags & EF_PRONE ) && ops->eFlags & EF_PRONE ) { if ( cg.weaponSelect == WP_MOBILE_MG42_SET ) { CG_FinishWeaponChange( cg.weaponSelect, ps->nextWeapon ); } } // run events CG_CheckPlayerstateEvents( ps, ops ); // smooth the ducking viewheight change if ( ps->viewheight != ops->viewheight ) { cg.duckChange = ps->viewheight - ops->viewheight; cg.duckTime = cg.time; } }