/*
===========================================================================
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.
===========================================================================
*/
/*
* name: cg_players.c
*
* desc: handle the media and animation for player entities
*
*/
#include "cg_local.h"
#define SWING_RIGHT 1
#define SWING_LEFT 2
#define JUMP_HEIGHT 56
#define SWINGSPEED 0.3
static int dp_realtime;
static float jumpHeight;
animation_t *lastTorsoAnim;
animation_t *lastLegsAnim;
extern const char* cg_skillRewards[SK_NUM_SKILLS][NUM_SKILL_LEVELS - 1];
/*
================
CG_EntOnFire
================
*/
qboolean CG_EntOnFire( centity_t *cent ) {
if ( cent->currentState.number == cg.snap->ps.clientNum ) {
// TAT 11/15/2002 - the player is always starting out on fire, which is easily seen in cinematics
// so make sure onFireStart is not 0
return ( cg.snap->ps.onFireStart
&& ( cg.snap->ps.onFireStart < cg.time )
&& ( ( cg.snap->ps.onFireStart + 2000 ) > cg.time ) );
} else {
return ( ( cent->currentState.onFireStart < cg.time ) &&
( cent->currentState.onFireEnd > cg.time ) );
}
}
/*
================
CG_IsCrouchingAnim
================
*/
qboolean CG_IsCrouchingAnim( animModelInfo_t* animModelInfo, int animNum ) {
animation_t *anim;
// FIXME: make compatible with new scripting
animNum &= ~ANIM_TOGGLEBIT;
//
anim = BG_GetAnimationForIndex( animModelInfo, animNum );
//
if ( anim->movetype & ( ( 1 << ANIM_MT_IDLECR ) | ( 1 << ANIM_MT_WALKCR ) | ( 1 << ANIM_MT_WALKCRBK ) ) ) {
return qtrue;
}
//
return qfalse;
}
/*
================
CG_CustomSound
================
*/
sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) {
if ( soundName[0] != '*' ) {
return trap_S_RegisterSound( soundName, qfalse );
}
return 0;
}
/*
=============================================================================
CLIENT INFO
=============================================================================
*/
/*
===================
CG_LoadClientInfo
Load it now, taking the disk hits.
This will usually be deferred to a safe time
===================
*/
static void CG_LoadClientInfo( int clientNum ) {
int i;
// reset any existing players and bodies, because they might be in bad
// frames for this new model
for ( i = 0 ; i < MAX_GENTITIES ; i++ ) {
if ( cg_entities[i].currentState.clientNum == clientNum && cg_entities[i].currentState.eType == ET_PLAYER ) {
CG_ResetPlayerEntity( &cg_entities[i] );
}
}
}
void CG_ParseTeamXPs( int n ) {
int i, j;
char* cs = (char*)CG_ConfigString( CS_AXIS_MAPS_XP + n );
const char* token;
for ( i = 0; i < MAX_MAPS_PER_CAMPAIGN; i++ ) {
for ( j = 0; j < SK_NUM_SKILLS; j++ ) {
token = COM_ParseExt( &cs, qfalse );
if ( !token || !*token ) {
return;
}
if ( n == 0 ) {
cgs.tdbAxisMapsXP[ j ][ i ] = atoi( token );
} else {
cgs.tdbAlliedMapsXP[ j ][ i ] = atoi( token );
}
}
}
}
void CG_LimboPanel_SendSetupMsg( qboolean forceteam );
/*
======================
CG_NewClientInfo
======================
*/
void CG_NewClientInfo( int clientNum ) {
clientInfo_t *ci;
clientInfo_t newInfo;
const char *configstring;
const char *v;
int oldclass;
ci = &cgs.clientinfo[clientNum];
configstring = CG_ConfigString( clientNum + CS_PLAYERS );
if ( !*configstring ) {
memset( ci, 0, sizeof( *ci ) );
return; // player just left
}
// build into a temp buffer so the defer checks can use
// the old value
memset( &newInfo, 0, sizeof( newInfo ) );
// Gordon: grabbing some older stuff, if it's a new client, tinfo will update within one second anyway, otherwise you get the health thing flashing red
// NOTE: why are we bothering to do all this setting up of a new clientInfo_t anyway? it was all for deffered clients iirc, which we dont have
newInfo.location[0] = ci->location[0];
newInfo.location[1] = ci->location[1];
newInfo.health = ci->health;
newInfo.fireteamData = ci->fireteamData;
newInfo.clientNum = clientNum;
newInfo.selected = ci->selected;
newInfo.totalWeapAcc = ci->totalWeapAcc;
// isolate the player's name
v = Info_ValueForKey( configstring, "n" );
Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );
Q_strncpyz( newInfo.cleanname, v, sizeof( newInfo.cleanname ) );
Q_CleanStr( newInfo.cleanname );
// bot skill
v = Info_ValueForKey( configstring, "skill" );
newInfo.botSkill = atoi( v );
// team
v = Info_ValueForKey( configstring, "t" );
newInfo.team = atoi( v );
// class
v = Info_ValueForKey( configstring, "c" );
newInfo.cls = atoi( v );
// rank
v = Info_ValueForKey( configstring, "r" );
newInfo.rank = atoi( v );
v = Info_ValueForKey( configstring, "f" );
newInfo.fireteam = atoi( v );
v = Info_ValueForKey( configstring, "m" );
if ( *v ) {
int i;
char buf[2];
buf[1] = '\0';
for ( i = 0; i < SK_NUM_SKILLS; i++ ) {
buf[0] = *v;
newInfo.medals[i] = atoi( buf );
v++;
}
}
v = Info_ValueForKey( configstring, "ch" );
if ( *v ) {
newInfo.character = cgs.gameCharacters[atoi( v )];
}
v = Info_ValueForKey( configstring, "s" );
if ( *v ) {
int i;
for ( i = 0; i < SK_NUM_SKILLS; i++ ) {
char skill[2];
skill[0] = v[i];
skill[1] = '\0';
newInfo.skill[i] = atoi( skill );
}
}
// diguiseName
v = Info_ValueForKey( configstring, "dn" );
Q_strncpyz( newInfo.disguiseName, v, sizeof( newInfo.disguiseName ) );
// disguiseRank
v = Info_ValueForKey( configstring, "dr" );
newInfo.disguiseRank = atoi( v );
// Gordon: weapon and latchedweapon ( FIXME: make these more secure )
v = Info_ValueForKey( configstring, "w" );
newInfo.weapon = atoi( v );
v = Info_ValueForKey( configstring, "lw" );
newInfo.latchedweapon = atoi( v );
v = Info_ValueForKey( configstring, "sw" );
newInfo.secondaryweapon = atoi( v );
v = Info_ValueForKey( configstring, "ref" );
newInfo.refStatus = atoi( v );
// Gordon: detect rank/skill changes client side
if ( clientNum == cg.clientNum ) {
int i;
if ( newInfo.team != cgs.clientinfo[ cg.clientNum ].team ) {
if ( cgs.autoFireteamCreateEndTime != cg.time + 20000 ) {
cgs.autoFireteamCreateEndTime = 0;
}
if ( cgs.autoFireteamJoinEndTime != cg.time + 20000 ) {
cgs.autoFireteamJoinEndTime = 0;
}
}
if ( newInfo.rank > cgs.clientinfo[ cg.clientNum ].rank ) {
CG_SoundPlaySoundScript( cgs.clientinfo[cg.clientNum].team == TEAM_ALLIES ? rankSoundNames_Allies[ newInfo.rank ] : rankSoundNames_Axis[ newInfo.rank ], NULL, -1, qtrue );
CG_AddPMItemBig( PM_RANK, va( "Promoted to rank %s!", cgs.clientinfo[ cg.clientNum ].team == TEAM_AXIS ? rankNames_Axis[newInfo.rank] : rankNames_Allies[newInfo.rank] ), rankicons[ newInfo.rank ][ 0 ].shader );
}
for ( i = 0; i < SK_NUM_SKILLS; i++ ) {
if ( newInfo.skill[i] > cgs.clientinfo[ cg.clientNum ].skill[i] ) {
// Gordon: slick hack so that funcs we call use teh new value now
cgs.clientinfo[ cg.clientNum ].skill[ i ] = newInfo.skill[ i ];
if ( newInfo.skill[i] == 4 && i == SK_HEAVY_WEAPONS ) {
if ( cgs.clientinfo[ cg.clientNum ].skill[SK_LIGHT_WEAPONS] == 4 ) {
oldclass = cgs.ccSelectedClass;
cgs.ccSelectedClass = newInfo.cls;
CG_LimboPanel_SetSelectedWeaponNumForSlot( 1, 2 );
CG_LimboPanel_SendSetupMsg( qfalse );
cgs.ccSelectedClass = oldclass;
} else {
oldclass = cgs.ccSelectedClass;
cgs.ccSelectedClass = newInfo.cls;
CG_LimboPanel_SetSelectedWeaponNumForSlot( 1, 1 );
CG_LimboPanel_SendSetupMsg( qfalse );
cgs.ccSelectedClass = oldclass;
}
}
if ( newInfo.skill[i] == 4 && i == SK_LIGHT_WEAPONS ) {
if ( cgs.clientinfo[ cg.clientNum ].skill[SK_HEAVY_WEAPONS] == 4 ) {
if ( cgs.ccSelectedWeapon2 == 2 ) {
oldclass = cgs.ccSelectedClass;
cgs.ccSelectedClass = newInfo.cls;
CG_LimboPanel_SetSelectedWeaponNumForSlot( 1, 3 );
CG_LimboPanel_SendSetupMsg( qfalse );
cgs.ccSelectedClass = oldclass;
}
} else {
oldclass = cgs.ccSelectedClass;
cgs.ccSelectedClass = newInfo.cls;
CG_LimboPanel_SetSelectedWeaponNumForSlot( 1, 1 );
CG_LimboPanel_SendSetupMsg( qfalse );
cgs.ccSelectedClass = oldclass;
}
}
CG_AddPMItemBig( PM_SKILL, va( "Increased %s skill to level %i!", skillNames[i], newInfo.skill[i] ), cgs.media.skillPics[ i ] );
CG_PriorityCenterPrint( va( "You have been rewarded with %s", cg_skillRewards[ i ][ newInfo.skill[i] - 1 ] ), SCREEN_HEIGHT - ( SCREEN_HEIGHT * 0.20 ), SMALLCHAR_WIDTH, 99999 );
}
}
if ( newInfo.team != cgs.clientinfo[ cg.clientNum ].team ) {
// clear these
memset( cg.artilleryRequestPos, 0, sizeof( cg.artilleryRequestPos ) );
memset( cg.artilleryRequestTime, 0, sizeof( cg.artilleryRequestTime ) );
}
trap_Cvar_Set( "authLevel", va( "%i", newInfo.refStatus ) );
if ( newInfo.refStatus != ci->refStatus ) {
if ( newInfo.refStatus <= RL_NONE ) {
const char *info = CG_ConfigString( CS_SERVERINFO );
trap_Cvar_Set( "cg_ui_voteFlags", Info_ValueForKey( info, "voteFlags" ) );
CG_Printf( "[cgnotify]^3*** You have been stripped of your referee status! ***\n" );
} else {
trap_Cvar_Set( "cg_ui_voteFlags", "0" );
CG_Printf( "[cgnotify]^2*** You have been authorized \"%s\" status ***\n", ( ( newInfo.refStatus == RL_RCON ) ? "rcon" : "referee" ) );
CG_Printf( "Type: ^3ref^7 (by itself) for a list of referee commands.\n" );
}
}
}
// rain - passing the clientNum since that's all we need, and we
// can't calculate it properly from the clientinfo
CG_LoadClientInfo( clientNum );
// replace whatever was there with the new one
newInfo.infoValid = qtrue;
*ci = newInfo;
// make sure we have a character set
if ( !ci->character ) {
ci->character = BG_GetCharacter( ci->team, ci->cls );
}
// Gordon: need to resort the fireteam list, incase ranks etc have changed
CG_SortClientFireteam();
}
/*
=============================================================================
PLAYER ANIMATION
=============================================================================
*/
bg_playerclass_t* CG_PlayerClassForClientinfo( clientInfo_t *ci, centity_t* cent ) {
int team, cls;
if ( cent && cent->currentState.eType == ET_CORPSE ) {
return BG_GetPlayerClassInfo( cent->currentState.modelindex, cent->currentState.modelindex2 );
}
if ( cent && cent->currentState.powerups & ( 1 << PW_OPS_DISGUISED ) ) {
team = ci->team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS;
// rain - fixed incorrect class determination (was & 6,
// should be & 7)
cls = ( cent->currentState.powerups >> PW_OPS_CLASS_1 ) & 7;
return BG_GetPlayerClassInfo( team, cls );
}
return BG_GetPlayerClassInfo( ci->team, ci->cls );
}
/*
===============
CG_SetLerpFrameAnimation
may include ANIM_TOGGLEBIT
===============
*/
static void CG_SetLerpFrameAnimation( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
animation_t *anim;
bg_character_t* character = CG_CharacterForClientinfo( ci, cent );
if ( !character ) {
return;
}
lf->animationNumber = newAnimation;
newAnimation &= ~ANIM_TOGGLEBIT;
if ( newAnimation < 0 || newAnimation >= character->animModelInfo->numAnimations ) {
CG_Error( "CG_SetLerpFrameAnimation: Bad animation number: %i", newAnimation );
}
anim = character->animModelInfo->animations[ newAnimation ];
lf->animation = anim;
lf->animationTime = lf->frameTime + anim->initialLerp;
if ( cg_debugAnim.integer == 1 ) {
CG_Printf( "Anim: %i, %s\n", newAnimation, character->animModelInfo->animations[newAnimation]->name );
}
}
/*
===============
CG_RunLerpFrame
Sets cg.snap, cg.oldFrame, and cg.backlerp
cg.time should be between oldFrameTime and frameTime after exit
===============
*/
void CG_RunLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) {
int f;
animation_t *anim;
// debugging tool to get no animations
if ( cg_animSpeed.integer == 0 ) {
lf->oldFrame = lf->frame = lf->backlerp = 0;
return;
}
// see if the animation sequence is switching
if ( ci && ( newAnimation != lf->animationNumber || !lf->animation ) ) {
CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation );
}
// if we have passed the current frame, move it to
// oldFrame and calculate a new frame
if ( cg.time >= lf->frameTime ) {
lf->oldFrame = lf->frame;
lf->oldFrameTime = lf->frameTime;
lf->oldFrameModel = lf->frameModel;
// get the next frame based on the animation
anim = lf->animation;
if ( !anim->frameLerp ) {
return; // shouldn't happen
}
if ( cg.time < lf->animationTime ) {
lf->frameTime = lf->animationTime; // initial lerp
} else {
lf->frameTime = lf->oldFrameTime + anim->frameLerp;
}
f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
f *= speedScale; // adjust for haste, etc
if ( f >= anim->numFrames ) {
f -= anim->numFrames;
if ( anim->loopFrames ) {
f %= anim->loopFrames;
f += anim->numFrames - anim->loopFrames;
} else {
f = anim->numFrames - 1;
// the animation is stuck at the end, so it
// can immediately transition to another sequence
lf->frameTime = cg.time;
}
}
lf->frame = anim->firstFrame + f;
lf->frameModel = anim->mdxFile;
if ( cg.time > lf->frameTime ) {
lf->frameTime = cg.time;
if ( cg_debugAnim.integer ) {
CG_Printf( "Clamp lf->frameTime\n" );
}
}
}
if ( lf->frameTime > cg.time + 200 ) {
lf->frameTime = cg.time;
}
if ( lf->oldFrameTime > cg.time ) {
lf->oldFrameTime = cg.time;
}
// calculate current lerp value
if ( lf->frameTime == lf->oldFrameTime ) {
lf->backlerp = 0;
} else {
lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
}
}
/*
===============
CG_ClearLerpFrame
===============
*/
static void CG_ClearLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) {
lf->frameTime = lf->oldFrameTime = cg.time;
CG_SetLerpFrameAnimation( cent, ci, lf, animationNumber );
if ( lf->animation ) {
lf->oldFrame = lf->frame = lf->animation->firstFrame;
lf->oldFrameModel = lf->frameModel = lf->animation->mdxFile;
}
}
/*
===============
CG_SetLerpFrameAnimationRate
may include ANIM_TOGGLEBIT
===============
*/
void CG_SetLerpFrameAnimationRate( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
animation_t *anim, *oldanim;
int transitionMin = -1, oldAnimTime, oldAnimNum;
qboolean firstAnim = qfalse;
bg_character_t *character = CG_CharacterForClientinfo( ci, cent );
if ( !character ) {
return;
}
oldAnimTime = lf->animationTime;
oldanim = lf->animation;
oldAnimNum = lf->animationNumber;
if ( !lf->animation ) {
firstAnim = qtrue;
}
lf->animationNumber = newAnimation;
newAnimation &= ~ANIM_TOGGLEBIT;
if ( newAnimation < 0 || newAnimation >= character->animModelInfo->numAnimations ) {
CG_Error( "CG_SetLerpFrameAnimationRate: Bad animation number: %i", newAnimation );
}
anim = character->animModelInfo->animations[ newAnimation ];
lf->animation = anim;
lf->animationTime = lf->frameTime + anim->initialLerp;
if ( !( anim->flags & ANIMFL_FIRINGANIM ) || ( lf != ¢->pe.torso ) ) {
if ( ( lf == ¢->pe.legs ) && ( CG_IsCrouchingAnim( character->animModelInfo, newAnimation ) != CG_IsCrouchingAnim( character->animModelInfo, oldAnimNum ) ) ) {
if ( anim->moveSpeed || ( anim->movetype & ( ( 1 << ANIM_MT_TURNLEFT ) | ( 1 << ANIM_MT_TURNRIGHT ) ) ) ) { // if unknown movetype, go there faster
transitionMin = lf->frameTime + 200; // slowly raise/drop
} else {
transitionMin = lf->frameTime + 350; // slowly raise/drop
}
} else if ( anim->moveSpeed ) {
transitionMin = lf->frameTime + 120; // always do some lerping (?)
} else { // not moving, so take your time
transitionMin = lf->frameTime + 170; // always do some lerping (?)
}
if ( oldanim && oldanim->animBlend ) { //transitionMin < lf->frameTime + oldanim->animBlend) {
transitionMin = lf->frameTime + oldanim->animBlend;
lf->animationTime = transitionMin;
} else {
// slow down transitions according to speed
if ( anim->moveSpeed && lf->animSpeedScale < 1.0 ) {
lf->animationTime += anim->initialLerp;
}
if ( lf->animationTime < transitionMin ) {
lf->animationTime = transitionMin;
}
}
}
// if first anim, go immediately
if ( firstAnim ) {
lf->frameTime = cg.time - 1;
lf->animationTime = cg.time - 1;
lf->frame = anim->firstFrame;
lf->frameModel = anim->mdxFile;
}
if ( cg_debugAnim.integer == 1 ) { // DHM - Nerve :: extra debug info
CG_Printf( "Anim: %i, %s\n", newAnimation, character->animModelInfo->animations[newAnimation]->name );
}
}
/*
===============
CG_RunLerpFrameRate
Sets cg.snap, cg.oldFrame, and cg.backlerp
cg.time should be between oldFrameTime and frameTime after exit
===============
*/
void CG_RunLerpFrameRate( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, centity_t *cent, int recursion ) {
int f;
animation_t *anim, *oldAnim;
animation_t *otherAnim = NULL;
qboolean isLadderAnim;
#define ANIM_SCALEMAX_LOW 1.1
#define ANIM_SCALEMAX_HIGH 1.6
#define ANIM_SPEEDMAX_LOW 100
#define ANIM_SPEEDMAX_HIGH 20
// debugging tool to get no animations
if ( cg_animSpeed.integer == 0 ) {
lf->oldFrame = lf->frame = lf->backlerp = 0;
return;
}
isLadderAnim = lf->animation && ( lf->animation->flags & ANIMFL_LADDERANIM );
oldAnim = lf->animation;
// see if the animation sequence is switching
if ( newAnimation != lf->animationNumber || !lf->animation ) {
CG_SetLerpFrameAnimationRate( cent, ci, lf, newAnimation );
}
// Ridah, make sure the animation speed is updated when possible
anim = lf->animation;
// check for forcing last frame
if ( cent->currentState.eFlags & EF_FORCE_END_FRAME
// xkan, 12/27/2002 - In SP, corpse also stays at the last frame (of the death animation)
// so that the death animation can end up in different positions
// and the body will stay in that position
|| ( /*CG_IsSinglePlayer() &&*/ cent->currentState.eType == ET_CORPSE ) ) {
lf->oldFrame = lf->frame = anim->firstFrame + anim->numFrames - 1;
lf->oldFrameModel = lf->frameModel = anim->mdxFile;
lf->backlerp = 0;
return;
}
if ( anim->moveSpeed && lf->oldFrameSnapshotTime ) {
float moveSpeed;
// calculate the speed at which we moved over the last frame
if ( cg.latestSnapshotTime != lf->oldFrameSnapshotTime && cg.nextSnap ) {
if ( cent->currentState.number == cg.snap->ps.clientNum ) {
if ( isLadderAnim ) { // only use Z axis for speed
lf->oldFramePos[0] = cent->lerpOrigin[0];
lf->oldFramePos[1] = cent->lerpOrigin[1];
} else { // only use x/y axis
lf->oldFramePos[2] = cent->lerpOrigin[2];
}
moveSpeed = Distance( cent->lerpOrigin, lf->oldFramePos ) / ( (float)( cg.time - lf->oldFrameTime ) / 1000.0 );
} else {
if ( isLadderAnim ) { // only use Z axis for speed
lf->oldFramePos[0] = cent->currentState.pos.trBase[0];
lf->oldFramePos[1] = cent->currentState.pos.trBase[1];
}
moveSpeed = Distance( cent->lerpOrigin, lf->oldFramePos ) / ( (float)( cg.time - lf->oldFrameTime ) / 1000.0 );
}
//
// convert it to a factor of this animation's movespeed
lf->animSpeedScale = moveSpeed / (float)anim->moveSpeed;
lf->oldFrameSnapshotTime = cg.latestSnapshotTime;
}
} else {
// move at normal speed
lf->animSpeedScale = 1.0;
lf->oldFrameSnapshotTime = cg.latestSnapshotTime;
}
// adjust with manual setting (pain anims)
lf->animSpeedScale *= cent->pe.animSpeed;
// if we have passed the current frame, move it to
// oldFrame and calculate a new frame
if ( cg.time >= lf->frameTime ) {
lf->oldFrame = lf->frame;
lf->oldFrameTime = lf->frameTime;
lf->oldFrameModel = lf->frameModel;
VectorCopy( cent->lerpOrigin, lf->oldFramePos );
// restrict the speed range
if ( lf->animSpeedScale < 0.25 ) { // if it's too slow, then a really slow spped, combined with a sudden take-off, can leave them playing a really slow frame while they a moving really fast
if ( lf->animSpeedScale < 0.01 && isLadderAnim ) {
lf->animSpeedScale = 0.0;
} else {
lf->animSpeedScale = 0.25;
}
} else if ( lf->animSpeedScale > ANIM_SCALEMAX_LOW ) {
if ( !( anim->flags & ANIMFL_LADDERANIM ) ) {
// allow slower anims to speed up more than faster anims
if ( anim->moveSpeed > ANIM_SPEEDMAX_LOW ) {
lf->animSpeedScale = ANIM_SCALEMAX_LOW;
} else if ( anim->moveSpeed < ANIM_SPEEDMAX_HIGH ) {
if ( lf->animSpeedScale > ANIM_SCALEMAX_HIGH ) {
lf->animSpeedScale = ANIM_SCALEMAX_HIGH;
}
} else {
lf->animSpeedScale = ANIM_SCALEMAX_HIGH - ( ANIM_SCALEMAX_HIGH - ANIM_SCALEMAX_LOW ) * (float)( anim->moveSpeed - ANIM_SPEEDMAX_HIGH ) / (float)( ANIM_SPEEDMAX_LOW - ANIM_SPEEDMAX_HIGH );
}
} else if ( lf->animSpeedScale > 4.0 ) {
lf->animSpeedScale = 4.0;
}
}
if ( lf == ¢->pe.legs ) {
otherAnim = cent->pe.torso.animation;
} else if ( lf == ¢->pe.torso ) {
otherAnim = cent->pe.legs.animation;
}
// get the next frame based on the animation
if ( !lf->animSpeedScale ) {
// stopped on the ladder, so stay on the same frame
f = lf->frame - anim->firstFrame;
lf->frameTime += anim->frameLerp; // don't wait too long before starting to move again
} else if ( lf->oldAnimationNumber != lf->animationNumber &&
( !anim->moveSpeed || lf->oldFrame < anim->firstFrame || lf->oldFrame >= anim->firstFrame + anim->numFrames ) ) { // Ridah, added this so walking frames don't always get reset to 0, which can happen in the middle of a walking anim, which looks wierd
lf->frameTime = lf->animationTime; // initial lerp
if ( oldAnim && anim->moveSpeed ) { // keep locomotions going continuously
f = ( lf->frame - oldAnim->firstFrame ) + 1;
while ( f < 0 ) {
f += anim->numFrames;
}
} else {
f = 0;
}
} else if ( ( lf == ¢->pe.legs ) && otherAnim && !( anim->flags & ANIMFL_FIRINGANIM ) && ( ( lf->animationNumber & ~ANIM_TOGGLEBIT ) == ( cent->pe.torso.animationNumber & ~ANIM_TOGGLEBIT ) ) && ( !anim->moveSpeed ) ) {
// legs should synch with torso
f = cent->pe.torso.frame - otherAnim->firstFrame;
if ( f >= anim->numFrames || f < 0 ) {
f = 0; // wait at the start for the legs to catch up (assuming they are still in an old anim)
}
lf->frameTime = cent->pe.torso.frameTime;
} else if ( ( lf == ¢->pe.torso ) && otherAnim && !( anim->flags & ANIMFL_FIRINGANIM ) && ( ( lf->animationNumber & ~ANIM_TOGGLEBIT ) == ( cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT ) ) && ( otherAnim->moveSpeed ) ) {
// torso needs to sync with legs
f = cent->pe.legs.frame - otherAnim->firstFrame;
if ( f >= anim->numFrames || f < 0 ) {
f = 0; // wait at the start for the legs to catch up (assuming they are still in an old anim)
}
lf->frameTime = cent->pe.legs.frameTime;
} else {
lf->frameTime = lf->oldFrameTime + (int)( (float)anim->frameLerp * ( 1.0 / lf->animSpeedScale ) );
if ( lf->frameTime < cg.time ) {
lf->frameTime = cg.time;
}
// check for skipping frames (eg. death anims play in slo-mo if low framerate)
if ( anim->flags & ANIMFL_REVERSED ) {
if ( cg.time > lf->frameTime && !anim->moveSpeed ) {
f = ( anim->numFrames - 1 ) - ( ( lf->frame - anim->firstFrame ) - ( 1 + ( cg.time - lf->frameTime ) / anim->frameLerp ) );
} else {
f = ( anim->numFrames - 1 ) - ( ( lf->frame - anim->firstFrame ) - 1 );
}
} else {
if ( cg.time > lf->frameTime && !anim->moveSpeed ) {
f = ( lf->frame - anim->firstFrame ) + 1 + ( cg.time - lf->frameTime ) / anim->frameLerp;
} else {
f = ( lf->frame - anim->firstFrame ) + 1;
}
}
if ( f < 0 ) {
f = 0;
}
}
//f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
if ( f >= anim->numFrames ) {
f -= anim->numFrames;
if ( anim->loopFrames ) {
f %= anim->loopFrames;
f += anim->numFrames - anim->loopFrames;
} else {
f = anim->numFrames - 1;
// the animation is stuck at the end, so it
// can immediately transition to another sequence
lf->frameTime = cg.time;
}
}
if ( anim->flags & ANIMFL_REVERSED ) {
lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
lf->frameModel = anim->mdxFile;
} else {
lf->frame = anim->firstFrame + f;
lf->frameModel = anim->mdxFile;
}
if ( cg.time > lf->frameTime ) {
// Ridah, run the frame again until we move ahead of the current time, fixes walking speeds for zombie
if ( /*!anim->moveSpeed ||*/ recursion > 4 ) {
lf->frameTime = cg.time;
} else {
CG_RunLerpFrameRate( ci, lf, newAnimation, cent, recursion + 1 );
}
if ( cg_debugAnim.integer > 3 ) {
CG_Printf( "Clamp lf->frameTime\n" );
}
}
lf->oldAnimationNumber = lf->animationNumber;
}
// Gordon: BIG hack, occaisionaly (VERY occaisionally), the frametime gets totally wacked
if ( lf->frameTime > cg.time + 5000 ) {
lf->frameTime = cg.time;
}
if ( lf->oldFrameTime > cg.time ) {
lf->oldFrameTime = cg.time;
}
// calculate current lerp value
if ( lf->frameTime == lf->oldFrameTime ) {
lf->backlerp = 0;
} else {
lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
}
}
/*
===============
CG_ClearLerpFrameRate
===============
*/
void CG_ClearLerpFrameRate( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) {
lf->frameTime = lf->oldFrameTime = cg.time;
CG_SetLerpFrameAnimationRate( cent, ci, lf, animationNumber );
if ( lf->animation ) {
lf->oldFrame = lf->frame = lf->animation->firstFrame;
lf->oldFrameModel = lf->frameModel = lf->animation->mdxFile;
}
}
/*
===============
CG_PlayerAnimation
===============
*/
static void CG_PlayerAnimation( centity_t *cent, refEntity_t *body ) {
clientInfo_t *ci;
int clientNum;
int animIndex, tempIndex;
bg_character_t *character;
clientNum = cent->currentState.clientNum;
ci = &cgs.clientinfo[ clientNum ];
character = CG_CharacterForClientinfo( ci, cent );
if ( !character ) {
return;
}
if ( cg_noPlayerAnims.integer ) {
body->frame = body->oldframe = body->torsoFrame = body->oldTorsoFrame = 0;
body->frameModel = body->oldframeModel = body->torsoFrameModel = body->oldTorsoFrameModel = character->animModelInfo->animations[0]->mdxFile;
return;
}
// default to whatever the legs are currently doing
animIndex = cent->currentState.legsAnim;
// do the shuffle turn frames locally
if ( !( cent->currentState.eFlags & EF_DEAD ) && cent->pe.legs.yawing ) {
//CG_Printf("turn: %i\n", cg.time );
tempIndex = BG_GetAnimScriptAnimation( clientNum, character->animModelInfo, cent->currentState.aiState, ( cent->pe.legs.yawing == SWING_RIGHT ? ANIM_MT_TURNRIGHT : ANIM_MT_TURNLEFT ) );
if ( tempIndex > -1 ) {
animIndex = tempIndex;
}
}
// run the animation
CG_RunLerpFrameRate( ci, ¢->pe.legs, animIndex, cent, 0 );
body->oldframe = cent->pe.legs.oldFrame;
body->frame = cent->pe.legs.frame;
body->backlerp = cent->pe.legs.backlerp;
body->frameModel = cent->pe.legs.frameModel;
body->oldframeModel = cent->pe.legs.oldFrameModel;
CG_RunLerpFrameRate( ci, ¢->pe.torso, cent->currentState.torsoAnim, cent, 0 );
body->oldTorsoFrame = cent->pe.torso.oldFrame;
body->torsoFrame = cent->pe.torso.frame;
body->torsoBacklerp = cent->pe.torso.backlerp;
body->torsoFrameModel = cent->pe.torso.frameModel;
body->oldTorsoFrameModel = cent->pe.torso.oldFrameModel;
}
/*
=============================================================================
PLAYER ANGLES
=============================================================================
*/
/*
==================
CG_SwingAngles
==================
*/
static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance,
float speed, float *angle, qboolean *swinging ) {
float swing;
float move;
float scale;
if ( !*swinging ) {
// see if a swing should be started
swing = AngleSubtract( *angle, destination );
if ( swing > swingTolerance || swing < -swingTolerance ) {
*swinging = qtrue;
}
}
if ( !*swinging ) {
return;
}
// modify the speed depending on the delta
// so it doesn't seem so linear
swing = AngleSubtract( destination, *angle );
scale = fabs( swing );
scale *= 0.05;
if ( scale < 0.5 ) {
scale = 0.5;
}
// swing towards the destination angle
if ( swing >= 0 ) {
move = cg.frametime * scale * speed;
if ( move >= swing ) {
move = swing;
*swinging = qfalse;
} else {
*swinging = SWING_LEFT; // left
}
*angle = AngleMod( *angle + move );
} else if ( swing < 0 ) {
move = cg.frametime * scale * -speed;
if ( move <= swing ) {
move = swing;
*swinging = qfalse;
} else {
*swinging = SWING_RIGHT; // right
}
*angle = AngleMod( *angle + move );
}
// clamp to no more than tolerance
swing = AngleSubtract( destination, *angle );
if ( swing > clampTolerance ) {
*angle = AngleMod( destination - ( clampTolerance - 1 ) );
} else if ( swing < -clampTolerance ) {
*angle = AngleMod( destination + ( clampTolerance - 1 ) );
}
}
/*
=================
CG_AddPainTwitch
=================
*/
static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) {
int t;
float f;
int duration;
float direction;
if ( !cent->pe.animSpeed ) {
// we need to inititialize this stuff
cent->pe.painAnimLegs = -1;
cent->pe.painAnimTorso = -1;
cent->pe.animSpeed = 1.0;
}
if ( cent->currentState.eFlags & EF_DEAD ) {
cent->pe.painAnimLegs = -1;
cent->pe.painAnimTorso = -1;
cent->pe.animSpeed = 1.0;
return;
}
if ( cent->pe.painDuration ) {
duration = cent->pe.painDuration;
} else {
duration = PAIN_TWITCH_TIME;
}
direction = (float)duration * 0.085;
if ( direction > 30 ) {
direction = 30;
}
if ( direction < 10 ) {
direction = 10;
}
direction *= (float)( cent->pe.painDirection * 2 ) - 1;
t = cg.time - cent->pe.painTime;
if ( t >= duration ) {
return;
}
f = 1.0 - (float)t / duration;
if ( cent->pe.painDirection ) {
torsoAngles[ROLL] += 20 * f;
} else {
torsoAngles[ROLL] -= 20 * f;
}
}
/*
===============
CG_PlayerAngles
Handles seperate torso motion
legs pivot based on direction of movement
head always looks exactly at cent->lerpAngles
if motion < 20 degrees, show in head only
if < 45 degrees, also show in torso
===============
*/
static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
vec3_t legsAngles, torsoAngles, headAngles;
float dest;
vec3_t velocity;
float speed;
float clampTolerance;
int legsSet, torsoSet;
clientInfo_t *ci;
bg_character_t *character;
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
character = CG_CharacterForClientinfo( ci, cent );
if ( !character ) {
return;
}
legsSet = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
torsoSet = cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT;
VectorCopy( cent->lerpAngles, headAngles );
headAngles[YAW] = AngleMod( headAngles[YAW] );
VectorClear( legsAngles );
VectorClear( torsoAngles );
// --------- yaw -------------
// allow yaw to drift a bit, unless these conditions don't allow them
if ( !( BG_GetConditionBitFlag( cent->currentState.clientNum, ANIM_COND_MOVETYPE, ANIM_MT_IDLE ) ||
BG_GetConditionBitFlag( cent->currentState.clientNum, ANIM_COND_MOVETYPE, ANIM_MT_IDLECR ) )/*
|| (BG_GetConditionValue( cent->currentState.clientNum, ANIM_COND_MOVETYPE, qfalse ) & ((1<pe.torso.yawing = qtrue; // always center
cent->pe.torso.pitching = qtrue; // always center
cent->pe.legs.yawing = qtrue; // always center
// if firing, make sure torso and head are always aligned
} else if ( BG_GetConditionValue( cent->currentState.clientNum, ANIM_COND_FIRING, qtrue ) ) {
cent->pe.torso.yawing = qtrue; // always center
cent->pe.torso.pitching = qtrue; // always center
}
// adjust legs for movement dir
if ( cent->currentState.eFlags & EF_DEAD || cent->currentState.eFlags & EF_MOUNTEDTANK ) {
// don't let dead bodies twitch
legsAngles[YAW] = headAngles[YAW];
torsoAngles[YAW] = headAngles[YAW];
} else {
legsAngles[YAW] = headAngles[YAW] + cent->currentState.angles2[YAW];
if ( !( cent->currentState.eFlags & EF_FIRING ) ) {
torsoAngles[YAW] = headAngles[YAW] + 0.35 * cent->currentState.angles2[YAW];
clampTolerance = 90;
} else { // must be firing
torsoAngles[YAW] = headAngles[YAW]; // always face firing direction
//if (fabs(cent->currentState.angles2[YAW]) > 30)
// legsAngles[YAW] = headAngles[YAW];
clampTolerance = 60;
}
// torso
CG_SwingAngles( torsoAngles[YAW], 25, clampTolerance, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing );
// if the legs are yawing (facing heading direction), allow them to rotate a bit, so we don't keep calling
// the legs_turn animation while an AI is firing, and therefore his angles will be randomizing according to their accuracy
clampTolerance = 150;
if ( BG_GetConditionBitFlag( ci->clientNum, ANIM_COND_MOVETYPE, ANIM_MT_IDLE ) ) {
cent->pe.legs.yawing = qfalse; // set it if they really need to swing
CG_SwingAngles( legsAngles[YAW], 20, clampTolerance, 0.5 * cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
} else if ( strstr( BG_GetAnimString( character->animModelInfo, legsSet ), "strafe" ) ) {
// FIXME: what is this strstr hack??
//if ( BG_GetConditionValue( ci->clientNum, ANIM_COND_MOVETYPE, qfalse ) & ((1<pe.legs.yawing = qfalse; // set it if they really need to swing
legsAngles[YAW] = headAngles[YAW];
CG_SwingAngles( legsAngles[YAW], 0, clampTolerance, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
} else if ( cent->pe.legs.yawing ) {
CG_SwingAngles( legsAngles[YAW], 0, clampTolerance, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
} else {
CG_SwingAngles( legsAngles[YAW], 40, clampTolerance, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
}
torsoAngles[YAW] = cent->pe.torso.yawAngle;
legsAngles[YAW] = cent->pe.legs.yawAngle;
}
// --------- pitch -------------
// only show a fraction of the pitch angle in the torso
if ( headAngles[PITCH] > 180 ) {
dest = ( -360 + headAngles[PITCH] ) * 0.75;
} else {
dest = headAngles[PITCH] * 0.75;
}
//CG_SwingAngles( dest, 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
//torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
if ( cent->currentState.eFlags & EF_PRONE ) {
torsoAngles[PITCH] = legsAngles[PITCH] - 3;
} else {
CG_SwingAngles( dest, 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
}
// --------- roll -------------
// lean towards the direction of travel
VectorCopy( cent->currentState.pos.trDelta, velocity );
speed = VectorNormalize( velocity );
if ( speed ) {
vec3_t axis[3];
float side;
speed *= 0.05;
AnglesToAxis( legsAngles, axis );
side = speed * DotProduct( velocity, axis[1] );
legsAngles[ROLL] -= side;
side = speed * DotProduct( velocity, axis[0] );
legsAngles[PITCH] += side;
}
// pain twitch
CG_AddPainTwitch( cent, torsoAngles );
// pull the angles back out of the hierarchial chain
AnglesSubtract( headAngles, torsoAngles, headAngles );
AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
AnglesToAxis( legsAngles, legs );
AnglesToAxis( torsoAngles, torso );
AnglesToAxis( headAngles, head );
}
/*
==============
CG_BreathPuffs
==============
*/
static void CG_BreathPuffs( centity_t *cent, refEntity_t *head ) {
clientInfo_t *ci;
vec3_t up, forward;
int contents;
vec3_t mang, morg, maxis[3];
ci = &cgs.clientinfo[ cent->currentState.number ];
if ( !cg_enableBreath.integer ) {
return;
}
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) {
return;
}
if ( !( cent->currentState.eFlags & EF_DEAD ) ) {
return;
}
// allow cg_enableBreath to force everyone to have breath
if ( !( cent->currentState.eFlags & EF_BREATH ) ) {
return;
}
contents = CG_PointContents( head->origin, 0 );
if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
return;
}
if ( ci->breathPuffTime > cg.time ) {
return;
}
CG_GetOriginForTag( cent, head, "tag_mouth", 0, morg, maxis );
AxisToAngles( maxis, mang );
AngleVectors( mang, forward, NULL, up );
//push the origin out a tad so it's not right in the guys face (tad==4)
VectorMA( morg, 4, forward, morg );
forward[0] = up[0] * 8 + forward[0] * 5;
forward[1] = up[1] * 8 + forward[1] * 5;
forward[2] = up[2] * 8 + forward[2] * 5;
CG_SmokePuff( morg, forward, 4, 1, 1, 1, 0.5f, 2000, cg.time, cg.time + 400, 0, cgs.media.shotgunSmokePuffShader );
ci->breathPuffTime = cg.time + 3000 + random() * 1000;
}
/*
===============
CG_TrailItem
===============
*/
/*static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) {
refEntity_t ent;
vec3_t angles;
qboolean ducking;
// DHM - Nerve :: Don't draw icon above your own head
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )
return;
memset( &ent, 0, sizeof( ent ) );
VectorCopy( cent->lerpAngles, angles );
angles[PITCH] = 0;
angles[ROLL] = 0;
AnglesToAxis( angles, ent.axis );
// DHM - Nerve :: adjusted values
VectorCopy( cent->lerpOrigin, ent.origin );
// Account for ducking
if ( cent->currentState.clientNum == cg.snap->ps.clientNum )
ducking = (cg.snap->ps.pm_flags & PMF_DUCKED);
else
ducking = (qboolean)cent->currentState.animMovetype;
if ( ducking )
ent.origin[2] += 38;
else
ent.origin[2] += 56;
ent.hModel = hModel;
trap_R_AddRefEntityToScene( &ent );
}*/
/*
===============
CG_PlayerFloatSprite
Float a sprite over the player's head
DHM - Nerve :: added height parameter
===============
*/
static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader, int height ) {
int rf;
refEntity_t ent;
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) {
rf = RF_THIRD_PERSON; // only show in mirrors
} else {
rf = 0;
}
memset( &ent, 0, sizeof( ent ) );
VectorCopy( cent->lerpOrigin, ent.origin );
ent.origin[2] += height; // DHM - Nerve :: was '48'
// Account for ducking
if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) {
if ( cg.snap->ps.pm_flags & PMF_DUCKED ) {
ent.origin[2] -= 18;
}
} else {
if ( (qboolean)cent->currentState.animMovetype ) {
ent.origin[2] -= 18;
}
}
ent.reType = RT_SPRITE;
ent.customShader = shader;
ent.radius = 6.66;
ent.renderfx = rf;
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 255;
ent.shaderRGBA[3] = 255;
trap_R_AddRefEntityToScene( &ent );
}
/*
===============
CG_PlayerSprites
Float sprites over the player's head
===============
*/
static void CG_PlayerSprites( centity_t *cent ) {
int team;
if ( cent->currentState.powerups & ( 1 << PW_REDFLAG ) ||
cent->currentState.powerups & ( 1 << PW_BLUEFLAG ) ) {
CG_PlayerFloatSprite( cent, cgs.media.objectiveShader, 56 );
return;
}
if ( cent->currentState.eFlags & EF_CONNECTION ) {
CG_PlayerFloatSprite( cent, cgs.media.disconnectIcon, 48 );
return;
}
if ( cent->currentState.powerups & ( 1 << PW_INVULNERABLE ) ) {
CG_PlayerFloatSprite( cent, cgs.media.spawnInvincibleShader, 56 );
return;
}
team = cgs.clientinfo[ cent->currentState.clientNum ].team;
// DHM - Nerve :: If this client is a medic, draw a 'revive' icon over
// dead players that are not in limbo yet.
if ( ( cent->currentState.eFlags & EF_DEAD )
&& cent->currentState.number == cent->currentState.clientNum
&& cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_MEDIC
&& cg.snap->ps.stats[ STAT_HEALTH ] > 0
&& cg.snap->ps.persistant[PERS_TEAM] == team ) {
CG_PlayerFloatSprite( cent, cgs.media.medicReviveShader, 8 );
return;
}
// DHM - Nerve :: show voice chat signal so players know who's talking
if ( cent->voiceChatSpriteTime > cg.time && cg.snap->ps.persistant[PERS_TEAM] == team ) {
CG_PlayerFloatSprite( cent, cent->voiceChatSprite, 56 );
return;
}
// DHM - Nerve :: only show talk icon to team-mates
if ( cent->currentState.eFlags & EF_TALK && cg.snap->ps.persistant[PERS_TEAM] == team ) {
CG_PlayerFloatSprite( cent, cgs.media.balloonShader, 48 );
return;
}
{
fireteamData_t* ft;
if ( ( ft = CG_IsOnFireteam( cent->currentState.number ) ) ) {
if ( ft == CG_IsOnFireteam( cg.clientNum ) && cgs.clientinfo[ cent->currentState.number ].selected ) {
CG_PlayerFloatSprite( cent, cgs.media.fireteamicons[ft->ident], 56 );
}
}
}
}
/*
===============
CG_PlayerShadow
Returns the Z component of the surface being shadowed
should it return a full plane instead of a Z?
===============
*/
#define SHADOW_DISTANCE 64
#define ZOFS 6.0
#define SHADOW_MIN_DIST 250.0
#define SHADOW_MAX_DIST 512.0
typedef struct {
char *tagname;
float size;
float maxdist;
float maxalpha;
qhandle_t shader;
} shadowPart_t;
static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) {
vec3_t end;
trace_t trace;
float dist, distFade;
int tagIndex, subIndex;
vec3_t origin, angles, axis[ 3 ];
vec4_t projection = { 0, 0, -1, 64 };
shadowPart_t shadowParts[] = {
{"tag_footleft", 10, 4, 1.0, 0},
{"tag_footright", 10, 4, 1.0, 0},
{"tag_torso", 18, 96, 0.8, 0},
{NULL, 0}
};
shadowParts[0].shader = cgs.media.shadowFootShader; //DAJ pulled out of initliization
shadowParts[1].shader = cgs.media.shadowFootShader;
shadowParts[2].shader = cgs.media.shadowTorsoShader;
*shadowPlane = 0;
if ( cg_shadows.integer == 0 ) {
return qfalse;
}
// send a trace down from the player to the ground
VectorCopy( cent->lerpOrigin, end );
end[2] -= SHADOW_DISTANCE;
trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, NULL, NULL, 0, MASK_PLAYERSOLID );
// no shadow if too high
//% if ( trace.fraction == 1.0 || trace.fraction == 0.0f ) {
//% return qfalse;
//% }
*shadowPlane = trace.endpos[2] + 1;
if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows
return qtrue;
}
// no shadows when dead
if ( cent->currentState.eFlags & EF_DEAD ) {
return qfalse;
}
// fade the shadow out with height
//% alpha = 1.0 - trace.fraction;
// add the mark as a temporary, so it goes directly to the renderer
// without taking a spot in the cg_marks array
dist = VectorDistance( cent->lerpOrigin, cg.refdef_current->vieworg ); //% cg.snap->ps.origin );
distFade = 1.0f;
if ( !( cent->currentState.eFlags & EF_ZOOMING ) && ( dist > SHADOW_MIN_DIST ) ) {
if ( dist > SHADOW_MAX_DIST ) {
if ( dist > SHADOW_MAX_DIST * 2 ) {
return qfalse;
} else { // fade out
distFade = 1.0f - ( ( dist - SHADOW_MAX_DIST ) / SHADOW_MAX_DIST );
}
if ( distFade > 1.0f ) {
distFade = 1.0f;
} else if ( distFade < 0.0f ) {
distFade = 0.0f;
}
}
// set origin
VectorCopy( cent->lerpOrigin, origin );
// project it onto the shadow plane
if ( origin[2] < *shadowPlane ) {
origin[2] = *shadowPlane;
}
// ydnar: add a bit of height so foot shadows don't clip into sloped geometry as much
origin[ 2 ] += 18.0f;
//% alpha *= distFade;
// ydnar: decal remix
//% CG_ImpactMark( cgs.media.shadowTorsoShader, trace.endpos, trace.plane.normal,
//% 0, alpha,alpha,alpha,1, qfalse, 16, qtrue, -1 );
CG_ImpactMark( cgs.media.shadowTorsoShader, origin, projection, 18.0f,
cent->lerpAngles[ YAW ], distFade, distFade, distFade, distFade, -1 );
return qtrue;
}
if ( dist < SHADOW_MAX_DIST ) { // show more detail
// now add shadows for the various body parts
for ( tagIndex = 0; shadowParts[tagIndex].tagname; tagIndex++ ) {
// grab each tag with this name
for ( subIndex = 0; ( subIndex = CG_GetOriginForTag( cent, ¢->pe.bodyRefEnt, shadowParts[tagIndex].tagname, subIndex, origin, axis ) ) >= 0; subIndex++ )
{
// project it onto the shadow plane
if ( origin[2] < *shadowPlane ) {
origin[2] = *shadowPlane;
}
// ydnar: add a bit of height so foot shadows don't clip into sloped geometry as much
origin[ 2 ] += 5.0f;
#if 0
alpha = 1.0 - ( ( origin[2] - ( *shadowPlane + ZOFS ) ) / shadowParts[tagIndex].maxdist );
if ( alpha < 0 ) {
continue;
}
if ( alpha > shadowParts[tagIndex].maxalpha ) {
alpha = shadowParts[tagIndex].maxalpha;
}
alpha *= ( 1.0 - distFade );
origin[2] = *shadowPlane;
#endif
AxisToAngles( axis, angles );
// ydnar: decal remix
//% CG_ImpactMark( shadowParts[tagIndex].shader, origin, trace.plane.normal,
//% angles[YAW]/*cent->pe.legs.yawAngle*/, alpha,alpha,alpha,1, qfalse, shadowParts[tagIndex].size, qtrue, -1 );
//% CG_ImpactMark( shadowParts[ tagIndex ].shader, origin, up,
//% cent->lerpAngles[ YAW ], 1.0f, 1.0f, 1.0f, 1.0f, qfalse, shadowParts[ tagIndex ].size, qtrue, -1 );
CG_ImpactMark( shadowParts[ tagIndex ].shader, origin, projection, shadowParts[ tagIndex ].size,
angles[ YAW ], distFade, distFade, distFade, distFade, -1 );
}
}
}
return qtrue;
}
/*
===============
CG_PlayerSplash
Draw a mark at the water surface
===============
*/
static void CG_PlayerSplash( centity_t *cent ) {
vec3_t start, end;
trace_t trace;
int contents;
polyVert_t verts[4];
if ( !cg_shadows.integer ) {
return;
}
VectorCopy( cent->lerpOrigin, end );
end[2] -= 24;
// if the feet aren't in liquid, don't make a mark
// this won't handle moving water brushes, but they wouldn't draw right anyway...
contents = CG_PointContents( end, 0 );
if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) {
return;
}
VectorCopy( cent->lerpOrigin, start );
start[2] += 32;
// if the head isn't out of liquid, don't make a mark
contents = CG_PointContents( start, 0 );
if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
return;
}
// trace down to find the surface
trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
if ( trace.fraction == 1.0 ) {
return;
}
// create a mark polygon
VectorCopy( trace.endpos, verts[0].xyz );
verts[0].xyz[0] -= 32;
verts[0].xyz[1] -= 32;
verts[0].st[0] = 0;
verts[0].st[1] = 0;
verts[0].modulate[0] = 255;
verts[0].modulate[1] = 255;
verts[0].modulate[2] = 255;
verts[0].modulate[3] = 255;
VectorCopy( trace.endpos, verts[1].xyz );
verts[1].xyz[0] -= 32;
verts[1].xyz[1] += 32;
verts[1].st[0] = 0;
verts[1].st[1] = 1;
verts[1].modulate[0] = 255;
verts[1].modulate[1] = 255;
verts[1].modulate[2] = 255;
verts[1].modulate[3] = 255;
VectorCopy( trace.endpos, verts[2].xyz );
verts[2].xyz[0] += 32;
verts[2].xyz[1] += 32;
verts[2].st[0] = 1;
verts[2].st[1] = 1;
verts[2].modulate[0] = 255;
verts[2].modulate[1] = 255;
verts[2].modulate[2] = 255;
verts[2].modulate[3] = 255;
VectorCopy( trace.endpos, verts[3].xyz );
verts[3].xyz[0] += 32;
verts[3].xyz[1] -= 32;
verts[3].st[0] = 1;
verts[3].st[1] = 0;
verts[3].modulate[0] = 255;
verts[3].modulate[1] = 255;
verts[3].modulate[2] = 255;
verts[3].modulate[3] = 255;
trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts );
}
//==========================================================================
/*
===============
CG_AddRefEntityWithPowerups
Adds a piece with modifications or duplications for powerups
Also called by CG_Missile for quad rockets, but nobody can tell...
===============
*/
void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team, entityState_t *es, const vec3_t fireRiseDir ) {
centity_t *cent;
refEntity_t backupRefEnt; //, parentEnt;
qboolean onFire = qfalse;
float alpha = 0.0;
float fireStart, fireEnd;
cent = &cg_entities[es->number];
ent->entityNum = es->number;
/* if (cent->pe.forceLOD) {
ent->reFlags |= REFLAG_FORCE_LOD;
}*/
backupRefEnt = *ent;
if ( CG_EntOnFire( &cg_entities[es->number] ) ) {
ent->reFlags |= REFLAG_FORCE_LOD;
}
trap_R_AddRefEntityToScene( ent );
if ( !onFire && CG_EntOnFire( &cg_entities[es->number] ) ) {
onFire = qtrue;
// set the alpha
if ( ent->entityNum == cg.snap->ps.clientNum ) {
fireStart = cg.snap->ps.onFireStart;
fireEnd = cg.snap->ps.onFireStart + 1500;
} else {
fireStart = es->onFireStart;
fireEnd = es->onFireEnd;
}
alpha = ( cg.time - fireStart ) / 1500.0;
if ( alpha > 1.0 ) {
alpha = ( fireEnd - cg.time ) / 1500.0;
if ( alpha > 1.0 ) {
alpha = 1.0;
}
}
}
if ( onFire ) {
if ( alpha < 0.0 ) {
alpha = 0.0;
}
ent->shaderRGBA[3] = ( unsigned char )( 255.0 * alpha );
VectorCopy( fireRiseDir, ent->fireRiseDir );
if ( VectorCompare( ent->fireRiseDir, vec3_origin ) ) {
VectorSet( ent->fireRiseDir, 0, 0, 1 );
}
ent->customShader = cgs.media.onFireShader;
trap_R_AddRefEntityToScene( ent );
ent->customShader = cgs.media.onFireShader2;
trap_R_AddRefEntityToScene( ent );
if ( ent->hModel == cent->pe.bodyRefEnt.hModel ) {
trap_S_AddLoopingSound( ent->origin, vec3_origin, cgs.media.flameCrackSound, (int)( 255.0 * alpha ), 0 );
}
}
*ent = backupRefEnt;
}
char *vtosf( const vec3_t v ) {
static int index;
static char str[8][64];
char *s;
// use an array so that multiple vtos won't collide
s = str[index];
index = ( index + 1 ) & 7;
Com_sprintf( s, 64, "(%f %f %f)", v[0], v[1], v[2] );
return s;
}
/*
===============
CG_AnimPlayerConditions
predict, or calculate condition for this entity, if it is not the local client
===============
*/
void CG_AnimPlayerConditions( bg_character_t *character, centity_t *cent ) {
entityState_t *es;
int legsAnim;
if ( !character ) {
return;
}
if ( cg.snap && cg.snap->ps.clientNum == cent->currentState.number && !cg.renderingThirdPerson ) {
return;
}
es = ¢->currentState;
// WEAPON
if ( es->eFlags & EF_ZOOMING ) {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_WEAPON, WP_BINOCULARS, qtrue );
} else {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_WEAPON, es->weapon, qtrue );
}
// MOUNTED
if ( ( es->eFlags & EF_MG42_ACTIVE ) || ( es->eFlags & EF_MOUNTEDTANK ) ) {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOUNTED, MOUNTED_MG42, qtrue );
} else if ( es->eFlags & EF_AAGUN_ACTIVE ) {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOUNTED, MOUNTED_AAGUN, qtrue );
} else {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOUNTED, MOUNTED_UNUSED, qtrue );
}
// UNDERHAND
BG_UpdateConditionValue( es->clientNum, ANIM_COND_UNDERHAND, cent->lerpAngles[0] > 0, qtrue );
if ( es->eFlags & EF_CROUCHING ) {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_CROUCHING, qtrue, qtrue );
} else {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_CROUCHING, qfalse, qtrue );
}
if ( es->eFlags & EF_FIRING ) {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_FIRING, qtrue, qtrue );
} else {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_FIRING, qfalse, qtrue );
}
// reverse engineer the legs anim -> movetype (if possible)
legsAnim = es->legsAnim & ~ANIM_TOGGLEBIT;
if ( character->animModelInfo->animations[legsAnim]->movetype ) {
BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOVETYPE, character->animModelInfo->animations[legsAnim]->movetype, qfalse );
}
}
/*
===============
CG_Player
===============
*/
void CG_Player( centity_t *cent ) {
clientInfo_t *ci;
refEntity_t body;
refEntity_t head;
refEntity_t acc;
vec3_t playerOrigin;
vec3_t lightorigin;
int clientNum,i;
int renderfx;
qboolean shadow;
float shadowPlane;
// float gumsflappin = 0; // talking amplitude
qboolean usingBinocs = qfalse;
centity_t *cgsnap;
bg_character_t *character;
float hilightIntensity = 0.f;
cgsnap = &cg_entities[cg.snap->ps.clientNum];
shadow = qfalse; // gjd added to make sure it was initialized
shadowPlane = 0.0; // ditto
// if set to invisible, skip
if ( cent->currentState.eFlags & EF_NODRAW ) {
return;
}
// the client number is stored in clientNum. It can't be derived
// from the entity number, because a single client may have
// multiple corpses on the level using the same clientinfo
clientNum = cent->currentState.clientNum;
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
CG_Error( "Bad clientNum on player entity" );
}
ci = &cgs.clientinfo[ clientNum ];
// it is possible to see corpses from disconnected players that may
// not have valid clientinfo
if ( !ci->infoValid ) {
return;
}
character = CG_CharacterForClientinfo( ci, cent );
if ( cent->currentState.eFlags & EF_MOUNTEDTANK ) {
VectorCopy( cg_entities[ cg_entities[ cent->currentState.clientNum ].tagParent ].mountedMG42Player.origin, playerOrigin );
} else if ( cent->currentState.eFlags & EF_MG42_ACTIVE || cent->currentState.eFlags & EF_AAGUN_ACTIVE ) { // Arnout: see if we're attached to a gun
centity_t *mg42;
int num;
// find the mg42 we're attached to
for ( num = 0 ; num < cg.snap->numEntities ; num++ ) {
mg42 = &cg_entities[ cg.snap->entities[ num ].number ];
if ( mg42->currentState.eType == ET_MG42_BARREL &&
mg42->currentState.otherEntityNum == cent->currentState.number ) {
// found it, clamp behind gun
vec3_t forward, right, up;
//AngleVectors (mg42->s.apos.trBase, forward, right, up);
AngleVectors( cent->lerpAngles, forward, right, up );
VectorMA( mg42->currentState.pos.trBase, -36, forward, playerOrigin );
playerOrigin[2] = cent->lerpOrigin[2];
break;
}
}
if ( num == cg.snap->numEntities ) {
VectorCopy( cent->lerpOrigin, playerOrigin );
}
} else {
VectorCopy( cent->lerpOrigin, playerOrigin );
}
memset( &body, 0, sizeof( body ) );
memset( &head, 0, sizeof( head ) );
memset( &acc, 0, sizeof( acc ) );
// get the rotation information
CG_PlayerAngles( cent, body.axis, body.torsoAxis, head.axis );
// FIXME: move this into CG_PlayerAngles
if ( cgsnap == cent && ( cg.snap->ps.pm_flags & PMF_LADDER ) ) {
memcpy( body.torsoAxis, body.axis, sizeof( body.torsoAxis ) );
}
// copy the torso rotation to the accessories
AxisCopy( body.torsoAxis, acc.axis );
// calculate client-side conditions
CG_AnimPlayerConditions( character, cent );
// get the animation state (after rotation, to allow feet shuffle)
CG_PlayerAnimation( cent, &body );
// forcibly set binoc animation
if ( cent->currentState.eFlags & EF_ZOOMING ) {
usingBinocs = qtrue;
}
// add the any sprites hovering above the player
// rain - corpses don't get icons (fireteam check ran out of bounds)
if ( cent->currentState.eType != ET_CORPSE ) {
CG_PlayerSprites( cent );
}
// add a water splash if partially in and out of water
CG_PlayerSplash( cent );
// get the player model information
renderfx = 0;
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) {
renderfx = RF_THIRD_PERSON; // only draw in mirrors
}
// draw the player in cameras
if ( cg.cameraMode ) {
renderfx &= ~RF_THIRD_PERSON;
}
if ( cg_shadows.integer == 3 && shadow ) {
renderfx |= RF_SHADOW_PLANE;
}
renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
// set renderfx for accessories
acc.renderfx = renderfx;
VectorCopy( playerOrigin, lightorigin );
lightorigin[2] += 31;
{
vec3_t dist;
vec_t distSquared;
VectorSubtract( lightorigin, cg.refdef_current->vieworg, dist );
distSquared = VectorLengthSquared( dist );
if ( distSquared > Square( 384.f ) ) {
renderfx |= RF_MINLIGHT;
distSquared -= Square( 384.f );
if ( distSquared > Square( 768.f ) ) {
hilightIntensity = 1.f;
} else {
hilightIntensity = 1.f * ( distSquared / Square( 768.f ) );
}
//CG_Printf( "%f\n", hilightIntensity );
}
}
body.hilightIntensity = hilightIntensity;
head.hilightIntensity = hilightIntensity;
acc.hilightIntensity = hilightIntensity;
//
// add the body
//
if ( cent->currentState.eType == ET_CORPSE && cent->currentState.time2 == 1 ) {
body.hModel = character->undressedCorpseModel;
body.customSkin = character->undressedCorpseSkin;
} else {
body.customSkin = character->skin;
body.hModel = character->mesh;
}
VectorCopy( playerOrigin, body.origin );
VectorCopy( lightorigin, body.lightingOrigin );
body.shadowPlane = shadowPlane;
body.renderfx = renderfx;
VectorCopy( body.origin, body.oldorigin ); // don't positionally lerp at all
cent->pe.bodyRefEnt = body;
// if the model failed, allow the default nullmodel to be displayed
// Gordon: whoever wrote that comment sucks
if ( !body.hModel ) {
return;
}
// (SA) only need to set this once...
VectorCopy( lightorigin, acc.lightingOrigin );
CG_AddRefEntityWithPowerups( &body, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir );
// ydnar debug
#if 0
{
int y;
vec3_t oldOrigin;
VectorCopy( body.origin, oldOrigin );
body.origin[ 0 ] -= 20;
//body.origin[ 0 ] -= 20 * 36;
for ( y = 0; y < 40; y++ )
{
body.origin[ 0 ] += 1;
//body.origin[ 0 ] += 36;
//body.origin[ 2 ] = BG_GetGroundHeightAtPoint( body.origin ) + (oldOrigin[2] - BG_GetGroundHeightAtPoint( oldOrigin ));
body.frame += ( y & 1 ) ? 1 : -1;
body.oldframe += ( y & 1 ) ? -1 : 1;
CG_AddRefEntityWithPowerups( &body, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir );
}
VectorCopy( oldOrigin, body.origin );
}
#endif
// DEBUG
/*{
int x, zd, zu;
vec3_t bmins, bmaxs;
x = (cent->currentState.solid & 255);
zd = ((cent->currentState.solid>>8) & 255);
zu = ((cent->currentState.solid>>16) & 255) - 32;
bmins[0] = bmins[1] = -x;
bmaxs[0] = bmaxs[1] = x;
bmins[2] = -zd;
bmaxs[2] = zu;
VectorAdd( bmins, cent->lerpOrigin, bmins );
VectorAdd( bmaxs, cent->lerpOrigin, bmaxs );
CG_RailTrail( NULL, bmins, bmaxs, 1 );
}*/
/*{
orientation_t tag;
int idx;
vec3_t start;
vec3_t ends[3];
vec3_t axis[3];
trap_R_LerpTag( &tag, &body, "tag_head", 0 );
VectorCopy( body.origin, start );
for( idx = 0; idx < 3; idx++ ) {
VectorMA( start, tag.origin[idx], body.axis[idx], start );
}
MatrixMultiply( tag.axis, body.axis, axis );
for( idx = 0; idx < 3; idx++ ) {
VectorMA( start, 32, axis[idx], ends[idx] );
CG_RailTrail2( NULL, start, ends[idx] );
}
}
{
vec3_t mins, maxs;
VectorCopy( cg.predictedPlayerState.mins, mins );
VectorCopy( cg.predictedPlayerState.maxs, maxs );
if( cg.predictedPlayerState.eFlags & EF_PRONE ) {
maxs[2] = maxs[2] - (cg.predictedPlayerState.standViewHeight - PRONE_VIEWHEIGHT + 8);
} else if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) {
maxs[2] = cg.predictedPlayerState.crouchMaxZ;
}
VectorAdd( cent->lerpOrigin, mins, mins );
VectorAdd( cent->lerpOrigin, maxs, maxs );
CG_RailTrail( NULL, mins, maxs, 1 );
if( cg.predictedPlayerState.eFlags & EF_PRONE ) {
vec3_t org, forward;
// The legs
VectorCopy( playerlegsProneMins, mins );
VectorCopy( playerlegsProneMaxs, maxs );
AngleVectors( cent->lerpAngles, forward, NULL, NULL );
forward[2] = 0;
VectorNormalizeFast( forward );
org[0] = cent->lerpOrigin[0] + forward[0] * -32;
org[1] = cent->lerpOrigin[1] + forward[1] * -32;
org[2] = cent->lerpOrigin[2] + cg.pmext.proneLegsOffset;
VectorAdd( org, mins, mins );
VectorAdd( org, maxs, maxs );
CG_RailTrail( NULL, mins, maxs, 1 );
// And the head
VectorSet( mins, -6, -6, -22 );
VectorSet( maxs, 6, 6, -10 );
org[0] = cent->lerpOrigin[0] + forward[0] * 12;
org[1] = cent->lerpOrigin[1] + forward[1] * 12;
org[2] = cent->lerpOrigin[2];
VectorAdd( org, mins, mins );
VectorAdd( org, maxs, maxs );
CG_RailTrail( NULL, mins, maxs, 1 );
}
}*/
// DEBUG
//
// add the head
//
if ( !( head.hModel = character->hudhead ) ) {
return;
}
head.customSkin = character->hudheadskin;
VectorCopy( lightorigin, head.lightingOrigin );
CG_PositionRotatedEntityOnTag( &head, &body, "tag_head" );
head.shadowPlane = shadowPlane;
head.renderfx = renderfx;
if ( cent->currentState.eFlags & EF_FIRING ) {
cent->pe.lastFiredWeaponTime = 0;
cent->pe.weaponFireTime += cg.frametime;
} else {
if ( cent->pe.weaponFireTime > 500 && cent->pe.weaponFireTime ) {
cent->pe.lastFiredWeaponTime = cg.time;
}
cent->pe.weaponFireTime = 0;
}
if ( cent->currentState.eType != ET_CORPSE && !( cent->currentState.eFlags & EF_DEAD ) ) {
hudHeadAnimNumber_t anim;
if ( cent->pe.weaponFireTime > 500 ) {
anim = HD_ATTACK;
} else if ( cg.time - cent->pe.lastFiredWeaponTime < 500 ) {
anim = HD_ATTACK_END;
} else {
anim = HD_IDLE1;
}
CG_HudHeadAnimation( character, ¢->pe.head, &head.oldframe, &head.frame, &head.backlerp, anim );
} else {
head.frame = 0;
head.oldframe = 0;
head.backlerp = 0.f;
}
// set blinking flag
CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir );
cent->pe.headRefEnt = head;
// add the
shadow = CG_PlayerShadow( cent, &shadowPlane );
// set the shadowplane for accessories
acc.shadowPlane = shadowPlane;
CG_BreathPuffs( cent, &head );
//
// add the gun / barrel / flash
//
if ( !( cent->currentState.eFlags & EF_DEAD ) /*&& !usingBinocs*/ ) { // NERVE - SMF
CG_AddPlayerWeapon( &body, NULL, cent );
}
//
// add binoculars (if it's not the player)
//
if ( usingBinocs ) { // NERVE - SMF
acc.hModel = cgs.media.thirdPersonBinocModel;
CG_PositionEntityOnTag( &acc, &body, "tag_weapon", 0, NULL );
CG_AddRefEntityWithPowerups( &acc, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir );
}
//
// add accessories
//
for ( i = ACC_BELT_LEFT; i < ACC_MAX; i++ ) {
if ( !( character->accModels[i] ) ) {
continue;
}
acc.hModel = character->accModels[i];
acc.customSkin = character->accSkins[i];
// Gordon: looted corpses dont have any accsserories, evil looters :E
if ( !( cent->currentState.eType == ET_CORPSE && cent->currentState.time2 == 1 ) ) {
switch ( i )
{
case ACC_BELT_LEFT:
CG_PositionEntityOnTag( &acc, &body, "tag_bright", 0, NULL );
break;
case ACC_BELT_RIGHT:
CG_PositionEntityOnTag( &acc, &body, "tag_bleft", 0, NULL );
break;
case ACC_BELT:
CG_PositionEntityOnTag( &acc, &body, "tag_ubelt", 0, NULL );
break;
case ACC_BACK:
CG_PositionEntityOnTag( &acc, &body, "tag_back", 0, NULL );
break;
case ACC_HAT: //hat
case ACC_RANK:
if ( cent->currentState.eFlags & EF_HEADSHOT ) {
continue;
}
case ACC_MOUTH2: // hat2
case ACC_MOUTH3: // hat3
if ( i == ACC_RANK ) {
if ( ci->rank == 0 ) {
continue;
}
acc.customShader = rankicons[ ci->rank ][ 1 ].shader;
}
CG_PositionEntityOnTag( &acc, &head, "tag_mouth", 0, NULL );
break;
// weapon and weapon2
// these are used by characters who have permanent weapons attached to their character in the skin
case ACC_WEAPON: // weap
CG_PositionEntityOnTag( &acc, &body, "tag_weapon", 0, NULL );
break;
case ACC_WEAPON2: // weap2
CG_PositionEntityOnTag( &acc, &body, "tag_weapon2", 0, NULL );
break;
default:
continue;
}
CG_AddRefEntityWithPowerups( &acc, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir );
}
}
}
//=====================================================================
extern void CG_ClearWeapLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber );
/*
===============
CG_ResetPlayerEntity
A player just came into view or teleported, so reset all animation info
===============
*/
void CG_ResetPlayerEntity( centity_t *cent ) {
// Gordon: these are unused
// cent->errorTime = -99999; // guarantee no error decay added
// cent->extrapolated = qfalse;
if ( !( cent->currentState.eFlags & EF_DEAD ) ) {
CG_ClearLerpFrameRate( cent, &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim );
CG_ClearLerpFrame( cent, &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim );
memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) );
cent->pe.legs.yawAngle = cent->rawAngles[YAW];
cent->pe.legs.yawing = qfalse;
cent->pe.legs.pitchAngle = 0;
cent->pe.legs.pitching = qfalse;
memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) );
cent->pe.torso.yawAngle = cent->rawAngles[YAW];
cent->pe.torso.yawing = qfalse;
cent->pe.torso.pitchAngle = cent->rawAngles[PITCH];
cent->pe.torso.pitching = qfalse;
}
BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin, qfalse, cent->currentState.effect2Time );
BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles, qtrue, cent->currentState.effect2Time );
VectorCopy( cent->lerpOrigin, cent->rawOrigin );
VectorCopy( cent->lerpAngles, cent->rawAngles );
if ( cg_debugPosition.integer ) {
CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
}
cent->pe.painAnimLegs = -1;
cent->pe.painAnimTorso = -1;
cent->pe.animSpeed = 1.0;
}
void CG_GetBleedOrigin( vec3_t head_origin, vec3_t body_origin, int fleshEntityNum ) {
clientInfo_t *ci;
refEntity_t body;
refEntity_t head;
centity_t *cent, backupCent;
bg_character_t *character;
ci = &cgs.clientinfo[ fleshEntityNum ];
if ( !ci->infoValid ) {
return;
}
character = CG_CharacterForClientinfo( ci, NULL );
cent = &cg_entities [ fleshEntityNum ];
backupCent = *cent;
memset( &body, 0, sizeof( body ) );
memset( &head, 0, sizeof( head ) );
CG_PlayerAngles( cent, body.axis, body.torsoAxis, head.axis );
CG_PlayerAnimation( cent, &body );
body.hModel = character->mesh;
if ( !body.hModel ) {
return;
}
head.hModel = character->hudhead;
if ( !head.hModel ) {
return;
}
VectorCopy( cent->lerpOrigin, body.origin );
VectorCopy( body.origin, body.oldorigin );
// Ridah, restore the cent so we don't interfere with animation timings
*cent = backupCent;
CG_PositionRotatedEntityOnTag( &head, &body, "tag_head" );
VectorCopy( head.origin, head_origin );
VectorCopy( body.origin, body_origin );
}
/*
===============
CG_GetTag
===============
*/
qboolean CG_GetTag( int clientNum, char *tagname, orientation_t *or ) {
clientInfo_t *ci;
centity_t *cent;
refEntity_t *refent;
vec3_t tempAxis[3];
vec3_t org;
int i;
ci = &cgs.clientinfo[ clientNum ];
if ( cg.snap && clientNum == cg.snap->ps.clientNum && cg.renderingThirdPerson ) {
cent = &cg.predictedPlayerEntity;
} else {
cent = &cg_entities[ci->clientNum];
if ( !cent->currentValid ) {
return qfalse; // not currently in PVS
}
}
refent = ¢->pe.bodyRefEnt;
if ( trap_R_LerpTag( or, refent, tagname, 0 ) < 0 ) {
return qfalse;
}
VectorCopy( refent->origin, org );
for ( i = 0 ; i < 3 ; i++ ) {
VectorMA( org, or->origin[i], refent->axis[i], org );
}
VectorCopy( org, or->origin );
// rotate with entity
MatrixMultiply( refent->axis, or->axis, tempAxis );
memcpy( or->axis, tempAxis, sizeof( vec3_t ) * 3 );
return qtrue;
}
/*
===============
CG_GetWeaponTag
===============
*/
qboolean CG_GetWeaponTag( int clientNum, char *tagname, orientation_t *or ) {
clientInfo_t *ci;
centity_t *cent;
refEntity_t *refent;
vec3_t tempAxis[3];
vec3_t org;
int i;
ci = &cgs.clientinfo[ clientNum ];
if ( cg.snap && clientNum == cg.snap->ps.clientNum && cg.renderingThirdPerson ) {
cent = &cg.predictedPlayerEntity;
} else {
cent = &cg_entities[ci->clientNum];
if ( !cent->currentValid ) {
return qfalse; // not currently in PVS
}
}
if ( cent->pe.gunRefEntFrame < cg.clientFrame - 1 ) {
return qfalse;
}
refent = ¢->pe.gunRefEnt;
if ( trap_R_LerpTag( or, refent, tagname, 0 ) < 0 ) {
return qfalse;
}
VectorCopy( refent->origin, org );
for ( i = 0 ; i < 3 ; i++ ) {
VectorMA( org, or->origin[i], refent->axis[i], org );
}
VectorCopy( org, or->origin );
// rotate with entity
MatrixMultiply( refent->axis, or->axis, tempAxis );
memcpy( or->axis, tempAxis, sizeof( vec3_t ) * 3 );
return qtrue;
}
// =============
// Menu Versions
// =============
/*
==================
CG_SwingAngles_Limbo
==================
*/
static void CG_SwingAngles_Limbo( float destination, float swingTolerance, float clampTolerance,
float speed, float *angle, qboolean *swinging ) {
float swing;
float move;
float scale;
if ( !*swinging ) {
// see if a swing should be started
swing = AngleSubtract( *angle, destination );
if ( swing > swingTolerance || swing < -swingTolerance ) {
*swinging = qtrue;
}
}
if ( !*swinging ) {
return;
}
// modify the speed depending on the delta
// so it doesn't seem so linear
swing = AngleSubtract( destination, *angle );
scale = fabs( swing );
if ( scale < swingTolerance * 0.5 ) {
scale = 0.5;
} else if ( scale < swingTolerance ) {
scale = 1.0;
} else {
scale = 2.0;
}
// swing towards the destination angle
if ( swing >= 0 ) {
move = cg.frametime * scale * speed;
if ( move >= swing ) {
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
} else if ( swing < 0 ) {
move = cg.frametime * scale * -speed;
if ( move <= swing ) {
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
}
// clamp to no more than tolerance
swing = AngleSubtract( destination, *angle );
if ( swing > clampTolerance ) {
*angle = AngleMod( destination - ( clampTolerance - 1 ) );
} else if ( swing < -clampTolerance ) {
*angle = AngleMod( destination + ( clampTolerance - 1 ) );
}
}
/*
===============
CG_PlayerAngles_Limbo
===============
*/
void CG_PlayerAngles_Limbo( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
vec3_t legsAngles, torsoAngles, headAngles;
float dest;
VectorCopy( pi->viewAngles, headAngles );
headAngles[YAW] = AngleMod( headAngles[YAW] );
VectorClear( legsAngles );
VectorClear( torsoAngles );
torsoAngles[YAW] = 180;
legsAngles[YAW] = 180;
headAngles[YAW] = 180;
// --------- pitch -------------
// only show a fraction of the pitch angle in the torso
if ( headAngles[PITCH] > 180 ) {
dest = ( -360 + headAngles[PITCH] ) * 0.75;
} else {
dest = headAngles[PITCH] * 0.75;
}
CG_SwingAngles_Limbo( dest, 15, 30, 0.1, &pi->torso.pitchAngle, &pi->torso.pitching );
torsoAngles[PITCH] = pi->torso.pitchAngle;
// pull the angles back out of the hierarchial chain
AnglesSubtract( headAngles, torsoAngles, headAngles );
AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
AnglesSubtract( legsAngles, pi->moveAngles, legsAngles ); // NERVE - SMF
AnglesToAxis( legsAngles, legs );
AnglesToAxis( torsoAngles, torso );
AnglesToAxis( headAngles, head );
}
animation_t *CG_GetLimboAnimation( playerInfo_t *pi, const char *name ) {
int i;
bg_character_t *character = BG_GetCharacter( pi->teamNum, pi->classNum );
if ( !character ) {
return NULL;
}
for ( i = 0; i < character->animModelInfo->numAnimations; i++ ) {
if ( !Q_stricmp( character->animModelInfo->animations[i]->name, name ) ) {
return character->animModelInfo->animations[i];
}
}
return character->animModelInfo->animations[0]; // safe fallback so we never end up without an animation (which will crash the game)
}
int CG_GetSelectedWeapon( void ) {
return 0;
}
void CG_DrawPlayer_Limbo( float x, float y, float w, float h, playerInfo_t *pi, int time, clientInfo_t *ci, qboolean animatedHead ) {
refdef_t refdef;
refEntity_t body;
refEntity_t head;
refEntity_t gun;
refEntity_t barrel;
refEntity_t acc;
vec3_t origin;
int renderfx;
vec3_t mins = {-16, -16, -24};
vec3_t maxs = {16, 16, 32};
float len;
// float xx;
vec4_t hcolor = { 1, 0, 0, 0.5 };
bg_character_t *character = BG_GetCharacter( pi->teamNum, pi->classNum );
int i;
dp_realtime = time;
CG_AdjustFrom640( &x, &y, &w, &h );
y -= jumpHeight;
memset( &refdef, 0, sizeof( refdef ) );
memset( &body, 0, sizeof( body ) );
memset( &head, 0, sizeof( head ) );
memset( &acc, 0, sizeof( acc ) );
refdef.rdflags = RDF_NOWORLDMODEL;
AxisClear( refdef.viewaxis );
refdef.x = x;
refdef.y = y;
refdef.width = w;
refdef.height = h;
/* refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
refdef.fov_y = atan2( refdef.height, xx );
refdef.fov_y *= ( 360 / M_PI );*/
refdef.fov_x = 35;
refdef.fov_y = 35;
// calculate distance so the player nearly fills the box
// START Mad Doc - TDF
// for "talking heads", we calc the origin differently
// FIXME - this is stupid - should all be character driven - NO CODE HACKS FOR SPECIFIC THINGS THAT CAN BE DONE CLEANLY
if ( animatedHead == qfalse ) {
// END Mad Doc - TDF
len = 0.9 * ( maxs[2] - mins[2] ); // NERVE - SMF - changed from 0.7
origin[0] = pi->y - 70 + ( len / tan( DEG2RAD( refdef.fov_x ) * 0.5 ) );
origin[1] = 0.5 * ( mins[1] + maxs[1] );
origin[2] = pi->z - 23 + ( -0.5 * ( mins[2] + maxs[2] ) );
} else
{
// for "talking head" animations, we want to center just below the face
// we precalculated this elsewhere
VectorCopy( pi->headOrigin, origin );
}
// END Mad Doc - TDF
refdef.time = dp_realtime;
trap_R_SetColor( hcolor );
trap_R_ClearScene();
trap_R_SetColor( NULL );
// get the rotation information
CG_PlayerAngles_Limbo( pi, body.axis, body.torsoAxis, head.axis );
renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
acc.renderfx = renderfx;
//
// add the body
//
body.hModel = character->mesh;
body.customSkin = character->skin;
body.renderfx = renderfx;
VectorCopy( origin, body.origin );
VectorCopy( origin, body.lightingOrigin );
VectorCopy( body.origin, body.oldorigin );
if ( cg.time >= pi->torso.frameTime ) {
pi->torso.oldFrameTime = pi->torso.frameTime;
pi->torso.oldFrame = pi->torso.frame;
pi->torso.oldFrameModel = pi->torso.frameModel = pi->torso.animation->mdxFile;
while ( cg.time >= pi->torso.frameTime ) {
pi->torso.frameTime += ( pi->torso.animation->duration / (float)( pi->torso.animation->numFrames ) );
pi->torso.frame++;
if ( pi->torso.frame >= pi->torso.animation->firstFrame + pi->torso.animation->numFrames ) {
pi->torso.frame = pi->torso.animation->firstFrame;
}
}
}
if ( pi->torso.frameTime == pi->torso.oldFrameTime ) {
pi->torso.backlerp = 0;
} else {
pi->torso.backlerp = 1.0 - (float)( cg.time - pi->torso.oldFrameTime ) / ( pi->torso.frameTime - pi->torso.oldFrameTime );
}
if ( cg.time >= pi->legs.frameTime ) {
pi->legs.oldFrameTime = pi->legs.frameTime;
pi->legs.oldFrame = pi->legs.frame;
pi->legs.oldFrameModel = pi->legs.frameModel = pi->legs.animation->mdxFile;
while ( cg.time >= pi->legs.frameTime ) {
pi->legs.frameTime += ( pi->legs.animation->duration / (float)( pi->legs.animation->numFrames ) );
pi->legs.frame++;
if ( pi->legs.frame >= pi->legs.animation->firstFrame + pi->legs.animation->numFrames ) {
pi->legs.frame = pi->legs.animation->firstFrame;
}
}
}
if ( pi->legs.frameTime == pi->legs.oldFrameTime ) {
pi->legs.backlerp = 0;
} else {
pi->legs.backlerp = 1.0 - (float)( cg.time - pi->legs.oldFrameTime ) / ( pi->legs.frameTime - pi->legs.oldFrameTime );
}
body.oldTorsoFrame = pi->torso.oldFrame;
body.torsoFrame = pi->torso.frame;
body.torsoBacklerp = pi->torso.backlerp;
body.torsoFrameModel = pi->torso.frameModel;
body.oldTorsoFrameModel = pi->torso.oldFrameModel;
body.oldframe = pi->legs.oldFrame;
body.frame = pi->legs.frame;
body.backlerp = pi->legs.backlerp;
body.frameModel = pi->legs.frameModel;
body.oldframeModel = pi->legs.oldFrameModel;
trap_R_AddRefEntityToScene( &body );
//
// add the head
//
head.hModel = character->hudhead;
if ( !head.hModel ) {
return;
}
head.customSkin = character->headSkin;
VectorCopy( origin, head.lightingOrigin );
CG_PositionRotatedEntityOnTag( &head, &body, "tag_head" );
head.renderfx = renderfx;
head.frame = 0;
head.oldframe = 0;
head.backlerp = 0.f;
trap_R_AddRefEntityToScene( &head );
AxisCopy( body.torsoAxis, acc.axis );
VectorCopy( origin, acc.lightingOrigin );
for ( i = ACC_BELT_LEFT; i < ACC_MAX; i++ ) {
if ( !( character->accModels[i] ) ) {
continue;
}
acc.hModel = character->accModels[i];
acc.customSkin = character->accSkins[i];
switch ( i ) {
case ACC_BELT_LEFT:
CG_PositionEntityOnTag( &acc, &body, "tag_bright", 0, NULL );
break;
case ACC_BELT_RIGHT:
CG_PositionEntityOnTag( &acc, &body, "tag_bleft", 0, NULL );
break;
case ACC_BELT:
CG_PositionEntityOnTag( &acc, &body, "tag_ubelt", 0, NULL );
break;
case ACC_BACK:
CG_PositionEntityOnTag( &acc, &body, "tag_back", 0, NULL );
break;
case ACC_HAT: // hat
case ACC_MOUTH2: // hat2
case ACC_MOUTH3: // hat3
CG_PositionEntityOnTag( &acc, &head, "tag_mouth", 0, NULL );
break;
// weapon and weapon2
// these are used by characters who have permanent weapons attached to their character in the skin
case ACC_WEAPON: // weap
CG_PositionEntityOnTag( &acc, &body, "tag_weapon", 0, NULL );
break;
case ACC_WEAPON2: // weap2
CG_PositionEntityOnTag( &acc, &body, "tag_weapon2", 0, NULL );
break;
default:
continue;
}
trap_R_AddRefEntityToScene( &acc );
}
//
// add the gun
//
{
int weap = CG_GetSelectedWeapon();
memset( &gun, 0, sizeof( gun ) );
memset( &barrel, 0, sizeof( barrel ) );
gun.hModel = cg_weapons[weap].weaponModel[W_TP_MODEL].model;
VectorCopy( origin, gun.lightingOrigin );
CG_PositionEntityOnTag( &gun, &body, "tag_weapon", 0, NULL );
gun.renderfx = renderfx;
trap_R_AddRefEntityToScene( &gun );
}
// Save out the old render info so we don't kill the LOD system here
trap_R_SaveViewParms();
//
// add an accent light
//
origin[0] -= 100; // + = behind, - = in front
origin[1] += 100; // + = left, - = right
origin[2] += 100; // + = above, - = below
//% trap_R_AddLightToScene( origin, 1000, 1.0, 1.0, 1.0, 0 );
trap_R_AddLightToScene( origin, 1000, 1.0, 1.0, 1.0, 1.0, 0, 0 );
origin[0] -= 100;
origin[1] -= 100;
origin[2] -= 100;
//% trap_R_AddLightToScene( origin, 1000, 1.0, 1.0, 1.0, 0 );
trap_R_AddLightToScene( origin, 1000, 1.0, 1.0, 1.0, 1.0, 0, 0 );
trap_R_RenderScene( &refdef );
// Reset the view parameters
trap_R_RestoreViewParms();
}
weaponType_t weaponTypes[] = {
{ WP_MP40, "MP 40" },
{ WP_THOMPSON, "THOMPSON" },
{ WP_STEN, "STEN", },
{ WP_PANZERFAUST, "PANZERFAUST", },
{ WP_FLAMETHROWER, "FLAMETHROWER", },
{ WP_KAR98, "K43", },
{ WP_CARBINE, "M1 GARAND", },
{ WP_FG42, "FG42", },
{ WP_GARAND, "M1 GARAND", },
{ WP_MOBILE_MG42, "MOBILE MG42", },
{ WP_K43, "K43", },
{ WP_MORTAR, "MORTAR", },
{ WP_COLT, "COLT", },
{ WP_LUGER, "LUGER", },
{ WP_AKIMBO_COLT, "AKIMBO COLTS", },
{ WP_AKIMBO_LUGER, "AKIMBO LUGERS",},
{ WP_SILENCED_COLT, "COLT", },
{ WP_SILENCER, "LUGER", },
{ WP_AKIMBO_SILENCEDCOLT, "AKIMBO COLTS", },
{ WP_AKIMBO_SILENCEDLUGER, "AKIMBO LUGERS",},
{ WP_NONE, NULL, },
{ -1, NULL, },
};
weaponType_t* WM_FindWeaponTypeForWeapon( weapon_t weapon ) {
weaponType_t* w = weaponTypes;
if ( !weapon ) {
return NULL;
}
while ( w->weapindex != -1 ) {
if ( w->weapindex == weapon ) {
return w;
}
w++;
}
return NULL;
}
void WM_RegisterWeaponTypeShaders() {
weaponType_t* w = weaponTypes;
while ( w->weapindex ) {
// w->shaderHandle = trap_R_RegisterShaderNoMip( w->shader );
w++;
}
}
void CG_MenuSetAnimation( playerInfo_t *pi, const char* legsAnim, const char* torsoAnim, qboolean force, qboolean clearpending ) {
lastLegsAnim = pi->legs.animation = CG_GetLimboAnimation( pi, legsAnim );
lastTorsoAnim = pi->torso.animation = CG_GetLimboAnimation( pi, torsoAnim );
if ( force ) {
pi->legs.oldFrame = pi->legs.frame = pi->legs.animation->firstFrame;
pi->torso.oldFrame = pi->torso.frame = pi->torso.animation->firstFrame;
pi->legs.frameTime = cg.time;
pi->torso.frameTime = cg.time;
pi->legs.oldFrameModel = pi->legs.frameModel = pi->legs.animation->mdxFile;
pi->torso.oldFrameModel = pi->torso.frameModel = pi->torso.animation->mdxFile;
pi->numPendingAnimations = 0;
} else {
pi->legs.oldFrame = pi->legs.frame;
pi->legs.oldFrameModel = pi->legs.frameModel;
pi->legs.frame = pi->legs.animation->firstFrame;
pi->torso.oldFrame = pi->torso.frame;
pi->torso.oldFrameModel = pi->torso.frameModel;
pi->torso.frame = pi->torso.animation->firstFrame;
pi->legs.frameTime += 200; // Give them some time to lerp between animations
pi->torso.frameTime += 200;
}
if ( clearpending ) {
pi->numPendingAnimations = 0;
}
}
void CG_MenuPendingAnimation( playerInfo_t *pi, const char* legsAnim, const char* torsoAnim, int delay ) {
if ( pi->numPendingAnimations >= 4 ) {
return;
}
if ( !pi->numPendingAnimations ) {
pi->pendingAnimations[pi->numPendingAnimations].pendingAnimationTime = cg.time + delay;
} else {
pi->pendingAnimations[pi->numPendingAnimations].pendingAnimationTime = pi->pendingAnimations[pi->numPendingAnimations - 1].pendingAnimationTime + delay;
}
pi->pendingAnimations[pi->numPendingAnimations].pendingLegsAnim = legsAnim;
pi->pendingAnimations[pi->numPendingAnimations].pendingTorsoAnim = torsoAnim;
lastLegsAnim = CG_GetLimboAnimation( pi, legsAnim );
lastTorsoAnim = CG_GetLimboAnimation( pi, torsoAnim );
pi->numPendingAnimations++;
}
void CG_MenuCheckPendingAnimation( playerInfo_t *pi ) {
int i;
if ( pi->numPendingAnimations <= 0 ) {
return;
}
if ( pi->pendingAnimations[0].pendingAnimationTime && pi->pendingAnimations[0].pendingAnimationTime < cg.time ) {
CG_MenuSetAnimation( pi, pi->pendingAnimations[0].pendingLegsAnim, pi->pendingAnimations[0].pendingTorsoAnim, qfalse, qfalse );
for ( i = 0; i < 3; i++ ) {
memcpy( &pi->pendingAnimations[i], &pi->pendingAnimations[i + 1], sizeof( pendingAnimation_t ) );
}
pi->numPendingAnimations--;
}
}
void CG_SetHudHeadLerpFrameAnimation( bg_character_t* ch, lerpFrame_t *lf, int newAnimation ) {
animation_t *anim;
lf->animationNumber = newAnimation;
newAnimation &= ~ANIM_TOGGLEBIT;
if ( newAnimation < 0 || newAnimation >= MAX_HD_ANIMATIONS ) {
CG_Error( "Bad animation number (CG_SetHudHeadLerpFrameAnimation): %i", newAnimation );
}
anim = &ch->hudheadanimations[ newAnimation ];
lf->animation = anim;
lf->animationTime = lf->frameTime + anim->initialLerp;
}
void CG_ClearHudHeadLerpFrame( bg_character_t* ch, lerpFrame_t *lf, int animationNumber ) {
lf->frameTime = lf->oldFrameTime = cg.time;
CG_SetHudHeadLerpFrameAnimation( ch, lf, animationNumber );
lf->oldFrame = lf->frame = lf->animation->firstFrame;
lf->oldFrameModel = lf->frameModel = lf->animation->mdxFile;
}
void CG_RunHudHeadLerpFrame( bg_character_t* ch, lerpFrame_t *lf, int newAnimation, float speedScale ) {
int f;
animation_t *anim;
// see if the animation sequence is switching
if ( !lf->animation ) {
CG_ClearHudHeadLerpFrame( ch, lf, newAnimation );
} else if ( newAnimation != lf->animationNumber ) {
CG_SetHudHeadLerpFrameAnimation( ch, lf, newAnimation );
}
// if we have passed the current frame, move it to
// oldFrame and calculate a new frame
if ( cg.time >= lf->frameTime ) {
lf->oldFrame = lf->frame;
lf->oldFrameTime = lf->frameTime;
lf->oldFrameModel = lf->frameModel;
// get the next frame based on the animation
anim = lf->animation;
if ( !anim->frameLerp ) {
return; // shouldn't happen
}
if ( cg.time < lf->animationTime ) {
lf->frameTime = lf->animationTime; // initial lerp
} else {
lf->frameTime = lf->oldFrameTime + anim->frameLerp;
}
f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
f *= speedScale; // adjust for haste, etc
if ( f >= anim->numFrames ) {
f -= anim->numFrames;
if ( anim->loopFrames ) {
f %= anim->loopFrames;
f += anim->numFrames - anim->loopFrames;
} else {
f = anim->numFrames - 1;
// the animation is stuck at the end, so it
// can immediately transition to another sequence
lf->frameTime = cg.time;
}
}
lf->frame = anim->firstFrame + f;
lf->frameModel = anim->mdxFile;
if ( cg.time > lf->frameTime ) {
lf->frameTime = cg.time;
}
}
if ( lf->frameTime > cg.time + 200 ) {
lf->frameTime = cg.time;
}
if ( lf->oldFrameTime > cg.time ) {
lf->oldFrameTime = cg.time;
}
// calculate current lerp value
if ( lf->frameTime == lf->oldFrameTime ) {
lf->backlerp = 0;
} else {
lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
}
}
void CG_HudHeadAnimation( bg_character_t* ch, lerpFrame_t* lf, int *oldframe, int *frame, float *backlerp, hudHeadAnimNumber_t animation ) {
// centity_t *cent = &cg.predictedPlayerEntity;
CG_RunHudHeadLerpFrame( ch, lf, (int)animation, 1.f );
*oldframe = lf->oldFrame;
*frame = lf->frame;
*backlerp = lf->backlerp;
}