mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-12-02 17:12:47 +00:00
19ee9179e8
Added cvar g_saberAutoDeflect1stPerson so saber is only ever used to deflect lasers in first person Made selector always draw using HMD YAW so it remains in front when turning i.r.l Updated the hand model (@baggyg) Updated the crawler text with the patron credits Added @MuadDib's menu changes Aligned all weapons Scaled the saber hilt down a little bit Co-Authored-By: Grant Bagwell <general@grantbagwell.co.uk>
6152 lines
186 KiB
C++
6152 lines
186 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 1999 - 2005, Id Software, Inc.
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "cg_local.h"
|
|
#include "../game/g_local.h"
|
|
#include "../game/b_local.h"
|
|
#define CG_PLAYERS_CPP
|
|
#include "cg_media.h"
|
|
#include "FxScheduler.h"
|
|
#include "../game/ghoul2_shared.h"
|
|
#include "../game/anims.h"
|
|
#include "../game/wp_saber.h"
|
|
#include "bg_local.h"
|
|
#include <JKVR/VrClientInfo.h>
|
|
|
|
#define LOOK_SWING_SCALE 0.5
|
|
|
|
#include "animtable.h"
|
|
|
|
|
|
/*
|
|
|
|
player entities generate a great deal of information from implicit ques
|
|
taken from the entityState_t
|
|
|
|
*/
|
|
|
|
qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *headModelName, const char *headSkinName,
|
|
const char *torsoModelName, const char *torsoSkinName,
|
|
const char *legsModelName, const char *legsSkinName );
|
|
|
|
void CG_PlayerAnimSounds( int animFileIndex, qboolean torso, int oldFrame, int frame, int entNum );
|
|
extern void BG_G2SetBoneAngles( centity_t *cent, gentity_t *gent, int boneIndex, const vec3_t angles, const int flags,
|
|
const Eorientations up, const Eorientations left, const Eorientations forward, qhandle_t *modelList );
|
|
extern void FX_BorgDeathSparkParticles( vec3_t origin, vec3_t angles, vec3_t vel, vec3_t user );
|
|
extern int PM_GetTurnAnim( gentity_t *gent, int anim );
|
|
extern int PM_AnimLength( int index, animNumber_t anim );
|
|
extern qboolean PM_InRoll( playerState_t *ps );
|
|
|
|
//Basic set of custom sounds that everyone needs
|
|
// (keep numbers in ascending order in order for variant-capping to work)
|
|
const char *cg_customBasicSoundNames[MAX_CUSTOM_BASIC_SOUNDS] =
|
|
{
|
|
"*death1.wav",
|
|
"*death2.wav",
|
|
"*death3.wav",
|
|
"*jump1.wav",
|
|
"*pain25.wav",
|
|
"*pain50.wav",
|
|
"*pain75.wav",
|
|
"*pain100.wav",
|
|
"*gurp1.wav",
|
|
"*gurp2.wav",
|
|
"*drown.wav",
|
|
"*gasp.wav",
|
|
"*land1.wav",
|
|
"*falling1.wav",
|
|
};
|
|
|
|
//Used as a supplement to the basic set for enemies and hazard team
|
|
// (keep numbers in ascending order in order for variant-capping to work)
|
|
const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS] =
|
|
{
|
|
"*anger1.wav", //Say when acquire an enemy when didn't have one before
|
|
"*anger2.wav",
|
|
"*anger3.wav",
|
|
"*victory1.wav", //Say when killed an enemy
|
|
"*victory2.wav",
|
|
"*victory3.wav",
|
|
"*confuse1.wav", //Say when confused
|
|
"*confuse2.wav",
|
|
"*confuse3.wav",
|
|
"*pushed1.wav", //Say when force-pushed
|
|
"*pushed2.wav",
|
|
"*pushed3.wav",
|
|
"*choke1.wav",
|
|
"*choke2.wav",
|
|
"*choke3.wav",
|
|
"*ffwarn.wav",
|
|
"*ffturn.wav",
|
|
};
|
|
|
|
//Used as a supplement to the basic set for stormtroopers
|
|
// (keep numbers in ascending order in order for variant-capping to work)
|
|
const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] =
|
|
{
|
|
"*chase1.wav",
|
|
"*chase2.wav",
|
|
"*chase3.wav",
|
|
"*cover1.wav",
|
|
"*cover2.wav",
|
|
"*cover3.wav",
|
|
"*cover4.wav",
|
|
"*cover5.wav",
|
|
"*detected1.wav",
|
|
"*detected2.wav",
|
|
"*detected3.wav",
|
|
"*detected4.wav",
|
|
"*detected5.wav",
|
|
"*lost1.wav",
|
|
"*outflank1.wav",
|
|
"*outflank2.wav",
|
|
"*escaping1.wav",
|
|
"*escaping2.wav",
|
|
"*escaping3.wav",
|
|
"*giveup1.wav",
|
|
"*giveup2.wav",
|
|
"*giveup3.wav",
|
|
"*giveup4.wav",
|
|
"*look1.wav",
|
|
"*look2.wav",
|
|
"*sight1.wav",
|
|
"*sight2.wav",
|
|
"*sight3.wav",
|
|
"*sound1.wav",
|
|
"*sound2.wav",
|
|
"*sound3.wav",
|
|
"*suspicious1.wav",
|
|
"*suspicious2.wav",
|
|
"*suspicious3.wav",
|
|
"*suspicious4.wav",
|
|
"*suspicious5.wav",
|
|
};
|
|
|
|
//Used as a supplement to the basic set for jedi
|
|
// (keep numbers in ascending order in order for variant-capping to work)
|
|
const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS] =
|
|
{
|
|
"*combat1.wav",
|
|
"*combat2.wav",
|
|
"*combat3.wav",
|
|
"*jdetected1.wav",
|
|
"*jdetected2.wav",
|
|
"*jdetected3.wav",
|
|
"*taunt1.wav",
|
|
"*taunt2.wav",
|
|
"*taunt3.wav",
|
|
"*jchase1.wav",
|
|
"*jchase2.wav",
|
|
"*jchase3.wav",
|
|
"*jlost1.wav",
|
|
"*jlost2.wav",
|
|
"*jlost3.wav",
|
|
"*deflect1.wav",
|
|
"*deflect2.wav",
|
|
"*deflect3.wav",
|
|
"*gloat1.wav",
|
|
"*gloat2.wav",
|
|
"*gloat3.wav",
|
|
"*pushfail.wav",
|
|
};
|
|
|
|
|
|
// done at registration time only...
|
|
//
|
|
// cuts down on sound-variant registration for low end machines,
|
|
// eg *gloat1.wav (plus...2,...3) can be capped to all be just *gloat1.wav
|
|
//
|
|
static const char *GetCustomSound_VariantCapped(const char *ppsTable[], int iEntryNum, qboolean bForceVariant1)
|
|
{
|
|
extern vmCvar_t cg_VariantSoundCap;
|
|
|
|
// const int iVariantCap = 2; // test
|
|
const int &iVariantCap = cg_VariantSoundCap.integer;
|
|
|
|
if (iVariantCap || bForceVariant1)
|
|
{
|
|
char *p = (char *)strchr(ppsTable[iEntryNum],'.');
|
|
if (p && p-2 > ppsTable[iEntryNum] && isdigit(p[-1]) && !isdigit(p[-2]))
|
|
{
|
|
int iThisVariant = p[-1]-'0';
|
|
|
|
if (iThisVariant > iVariantCap || bForceVariant1)
|
|
{
|
|
// ok, let's not load this variant, so pick a random one below the cap value...
|
|
//
|
|
for (int i=0; i<2; i++) // 1st pass, choose random, 2nd pass (if random not in list), choose xxx1, else fall through...
|
|
{
|
|
char sName[MAX_QPATH];
|
|
|
|
Q_strncpyz(sName, ppsTable[iEntryNum], sizeof(sName));
|
|
p = strchr(sName,'.');
|
|
if (p)
|
|
{
|
|
*p = '\0';
|
|
sName[strlen(sName)-1] = '\0'; // strip the digit
|
|
|
|
int iRandom = bForceVariant1 ? 1 : (!i ? Q_irand(1,iVariantCap) : 1);
|
|
|
|
strcat(sName,va("%d.wav",iRandom));
|
|
|
|
// does this exist in the entries before the original one?...
|
|
//
|
|
for (int iScanNum=0; iScanNum<iEntryNum; iScanNum++)
|
|
{
|
|
if (!Q_stricmp(ppsTable[iScanNum], sName))
|
|
{
|
|
// yeah, this entry is also present in the table, so ok to return it
|
|
//
|
|
return ppsTable[iScanNum];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// didn't find an entry corresponding to either the random name, or the xxxx1 version,
|
|
// so give up and drop through to return the original...
|
|
//
|
|
}
|
|
}
|
|
}
|
|
|
|
return ppsTable[iEntryNum];
|
|
}
|
|
|
|
static void CG_RegisterCustomSounds(clientInfo_t *ci, int iSoundEntryBase,
|
|
int iTableEntries, const char *ppsTable[], const char *psDir
|
|
)
|
|
{
|
|
for ( int i=0 ; i<iTableEntries; i++ )
|
|
{
|
|
const char *s = GetCustomSound_VariantCapped(ppsTable,i, qfalse);
|
|
if ( !s )
|
|
{
|
|
break; // fairly pointless code in original, since there are no NULL's in the table, but wtf...
|
|
}
|
|
|
|
sfxHandle_t hSFX = cgi_S_RegisterSound( va("sound/chars/%s/misc/%s", psDir, s + 1) );
|
|
if (hSFX == 0)
|
|
{
|
|
// hmmm... variant in table was missing, so forcibly-retry with %1 version (which we may have just tried, but wtf?)...
|
|
//
|
|
s = GetCustomSound_VariantCapped(ppsTable,i, qtrue);
|
|
hSFX = cgi_S_RegisterSound( va("sound/chars/%s/misc/%s", psDir, s + 1) );
|
|
//
|
|
// and fall through regardless...
|
|
//
|
|
}
|
|
|
|
ci->sounds[i + iSoundEntryBase] = hSFX;
|
|
}
|
|
}
|
|
|
|
|
|
//SB: Never render any player model if 1st person and using the saber
|
|
bool CG_getPlayer1stPersonSaber(const centity_t *cent) {
|
|
return (!cent->gent->NPC && !cg.renderingThirdPerson &&
|
|
cent->gent->client->ps.weapon == WP_SABER);
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
CG_CustomSound
|
|
|
|
NOTE: when you call this, check the value. If zero, do not try to play the sound.
|
|
Either that or when a sound that doesn't exist is played, don't play the null
|
|
sound honk and don't display the error message
|
|
|
|
================
|
|
*/
|
|
static sfxHandle_t CG_CustomSound( int entityNum, const char *soundName, int customSoundSet )
|
|
{
|
|
clientInfo_t *ci;
|
|
int i;
|
|
|
|
if ( soundName[0] != '*' )
|
|
{
|
|
return cgi_S_RegisterSound( soundName );
|
|
}
|
|
|
|
if ( !g_entities[entityNum].client )
|
|
{
|
|
// No client, this should never happen, so just use kyle's sounds
|
|
//ci = &g_entities[0].client->clientInfo;
|
|
ci = &cgs.clientinfo[entityNum];
|
|
}
|
|
else
|
|
{
|
|
ci = &g_entities[entityNum].client->clientInfo;
|
|
}
|
|
|
|
//FIXME: if the sound you want to play could not be found, pick another from the same
|
|
//general grouping? ie: if you want ff_2c and there is none, try ff_2b or ff_2a...
|
|
switch ( customSoundSet )
|
|
{
|
|
case CS_BASIC:
|
|
// There should always be a clientInfo structure if there is a client, but just make sure...
|
|
if ( ci )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_BASIC_SOUNDS && cg_customBasicSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customBasicSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CS_COMBAT:
|
|
// There should always be a clientInfo structure if there is a client, but just make sure...
|
|
if ( ci )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_COMBAT_SOUNDS && cg_customCombatSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customCombatSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CS_EXTRA:
|
|
// There should always be a clientInfo structure if there is a client, but just make sure...
|
|
if ( ci )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_EXTRA_SOUNDS && cg_customExtraSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customExtraSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CS_JEDI:
|
|
// There should always be a clientInfo structure if there is a client, but just make sure...
|
|
if ( ci )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_JEDI_SOUNDS && cg_customJediSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customJediSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
//no set specified, search all
|
|
if ( ci )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_BASIC_SOUNDS && cg_customBasicSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customBasicSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i];
|
|
}
|
|
}
|
|
for ( i = 0 ; i < MAX_CUSTOM_COMBAT_SOUNDS && cg_customCombatSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customCombatSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS];
|
|
}
|
|
}
|
|
for ( i = 0 ; i < MAX_CUSTOM_EXTRA_SOUNDS && cg_customExtraSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customExtraSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS];
|
|
}
|
|
}
|
|
for ( i = 0 ; i < MAX_CUSTOM_JEDI_SOUNDS && cg_customJediSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customJediSoundNames[i] ) )
|
|
{
|
|
return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
CG_Error( "Unknown custom sound: %s", soundName );
|
|
return 0;
|
|
}
|
|
|
|
void CG_TryPlayCustomSound( vec3_t origin, int entityNum, soundChannel_t channel, const char *soundName, int customSoundSet )
|
|
{
|
|
sfxHandle_t soundIndex = CG_CustomSound( entityNum, soundName, customSoundSet );
|
|
if ( !soundIndex )
|
|
{
|
|
return;
|
|
}
|
|
|
|
cgi_S_StartSound( origin, entityNum, channel, soundIndex );
|
|
}
|
|
/*
|
|
======================
|
|
CG_NewClientinfo
|
|
|
|
For player only, NPCs get them through NPC_stats and G_ModelIndex
|
|
======================
|
|
*/
|
|
void CG_NewClientinfo( int clientNum )
|
|
{
|
|
clientInfo_t *ci;
|
|
const char *configstring;
|
|
const char *v;
|
|
// const char *s;
|
|
// int i;
|
|
|
|
configstring = CG_ConfigString( clientNum + CS_PLAYERS );
|
|
|
|
if ( !configstring[0] )
|
|
{
|
|
return; // player just left
|
|
}
|
|
//ci = &cgs.clientinfo[clientNum];
|
|
if ( !(g_entities[clientNum].client) )
|
|
{
|
|
return;
|
|
}
|
|
ci = &g_entities[clientNum].client->clientInfo;
|
|
|
|
// isolate the player's name
|
|
v = Info_ValueForKey(configstring, "n");
|
|
Q_strncpyz( ci->name, v, sizeof( ci->name ) );
|
|
|
|
// handicap
|
|
v = Info_ValueForKey( configstring, "hc" );
|
|
ci->handicap = atoi( v );
|
|
|
|
// team
|
|
v = Info_ValueForKey( configstring, "t" );
|
|
ci->team = (team_t) atoi( v );
|
|
|
|
// legsModel
|
|
v = Info_ValueForKey( configstring, "legsModel" );
|
|
|
|
Q_strncpyz(g_entities[clientNum].client->renderInfo.legsModelName, v,
|
|
sizeof( g_entities[clientNum].client->renderInfo.legsModelName));
|
|
|
|
// torsoModel
|
|
v = Info_ValueForKey( configstring, "torsoModel" );
|
|
|
|
Q_strncpyz(g_entities[clientNum].client->renderInfo.torsoModelName, v,
|
|
sizeof( g_entities[clientNum].client->renderInfo.torsoModelName));
|
|
|
|
// headModel
|
|
v = Info_ValueForKey( configstring, "headModel" );
|
|
|
|
Q_strncpyz(g_entities[clientNum].client->renderInfo.headModelName, v,
|
|
sizeof( g_entities[clientNum].client->renderInfo.headModelName));
|
|
|
|
// sounds
|
|
cvar_t *sex = gi.cvar( "sex", "male", 0 );
|
|
if ( Q_stricmp("female", sex->string ) == 0 )
|
|
{
|
|
ci->customBasicSoundDir = "kyla";
|
|
}
|
|
else
|
|
{
|
|
ci->customBasicSoundDir = "kyle";
|
|
}
|
|
|
|
//player uses only the basic custom sound set, not the combat or extra
|
|
CG_RegisterCustomSounds(ci,
|
|
0, // int iSoundEntryBase,
|
|
MAX_CUSTOM_BASIC_SOUNDS, // int iTableEntries,
|
|
cg_customBasicSoundNames, // const char *ppsTable[],
|
|
ci->customBasicSoundDir // const char *psDir
|
|
);
|
|
|
|
ci->infoValid = qfalse;
|
|
}
|
|
|
|
/*
|
|
CG_RegisterNPCCustomSounds
|
|
*/
|
|
void CG_RegisterNPCCustomSounds( clientInfo_t *ci )
|
|
{
|
|
// const char *s;
|
|
// int i;
|
|
|
|
// sounds
|
|
|
|
if ( ci->customBasicSoundDir && ci->customBasicSoundDir[0] )
|
|
{
|
|
CG_RegisterCustomSounds(ci,
|
|
0, // int iSoundEntryBase,
|
|
MAX_CUSTOM_BASIC_SOUNDS, // int iTableEntries,
|
|
cg_customBasicSoundNames, // const char *ppsTable[],
|
|
ci->customBasicSoundDir // const char *psDir
|
|
);
|
|
}
|
|
|
|
if ( ci->customCombatSoundDir && ci->customCombatSoundDir[0] )
|
|
{
|
|
CG_RegisterCustomSounds(ci,
|
|
MAX_CUSTOM_BASIC_SOUNDS, // int iSoundEntryBase,
|
|
MAX_CUSTOM_COMBAT_SOUNDS, // int iTableEntries,
|
|
cg_customCombatSoundNames, // const char *ppsTable[],
|
|
ci->customCombatSoundDir // const char *psDir
|
|
);
|
|
}
|
|
|
|
if ( ci->customExtraSoundDir && ci->customExtraSoundDir[0] )
|
|
{
|
|
CG_RegisterCustomSounds(ci,
|
|
MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS, // int iSoundEntryBase,
|
|
MAX_CUSTOM_EXTRA_SOUNDS, // int iTableEntries,
|
|
cg_customExtraSoundNames, // const char *ppsTable[],
|
|
ci->customExtraSoundDir // const char *psDir
|
|
);
|
|
}
|
|
|
|
if ( ci->customJediSoundDir && ci->customJediSoundDir[0] )
|
|
{
|
|
CG_RegisterCustomSounds(ci,
|
|
MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS, // int iSoundEntryBase,
|
|
MAX_CUSTOM_JEDI_SOUNDS, // int iTableEntries,
|
|
cg_customJediSoundNames, // const char *ppsTable[],
|
|
ci->customJediSoundDir // const char *psDir
|
|
);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
void CG_RegisterNPCEffects( team_t team )
|
|
|
|
This should register all the shaders, models and sounds used by a specific type
|
|
of NPC's spawn, death and other miscellaneous effects. NOT WEAPON EFFECTS, as those
|
|
are taken care of in CG_RegisterWeapon
|
|
*/
|
|
void CG_RegisterNPCEffects( team_t team )
|
|
{
|
|
switch( team )
|
|
{
|
|
|
|
case TEAM_ENEMY:
|
|
break;
|
|
|
|
case TEAM_NEUTRAL:
|
|
break;
|
|
|
|
case TEAM_PLAYER:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PLAYER ANIMATION
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
qboolean ValidAnimFileIndex ( int index )
|
|
{
|
|
if ( index < 0 || index >= level.numKnownAnimFileSets )
|
|
{
|
|
Com_Printf( S_COLOR_RED "Bad animFileIndex: %d\n", index );
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
void ParseAnimationSndBlock(const char *asb_filename, animsounds_t *animSounds, animation_t *animations, int *i,const char **text_p)
|
|
{
|
|
const char *token;
|
|
char soundString[MAX_QPATH];
|
|
int lowestVal, highestVal;
|
|
int animNum, num, n;
|
|
|
|
// get past starting bracket
|
|
while(1)
|
|
{
|
|
token = COM_Parse( text_p );
|
|
if ( !Q_stricmp( token, "{" ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
animSounds += *i;
|
|
|
|
// read information for each frame
|
|
while ( 1 )
|
|
{
|
|
if (*i >= MAX_ANIM_SOUNDS)
|
|
{
|
|
CG_Error( "ParseAnimationSndBlock: animation number >= MAX_ANIM_SOUNDS(%i)", MAX_ANIM_SOUNDS );
|
|
}
|
|
// Get base frame of sequence
|
|
token = COM_Parse( text_p );
|
|
if ( !token || !token[0])
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( !Q_stricmp( token, "}" ) ) // At end of block
|
|
{
|
|
break;
|
|
}
|
|
|
|
//Compare to same table as animations used
|
|
// so we don't have to use actual numbers for animation first frames,
|
|
// just need offsets.
|
|
//This way when animation numbers change, this table won't have to be updated,
|
|
// at least not much.
|
|
animNum = GetIDForString(animTable, token);
|
|
if(animNum == -1)
|
|
{//Unrecognized ANIM ENUM name, or we're skipping this line, keep going till you get a good one
|
|
Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animSound file %s\n", token, asb_filename );
|
|
continue;
|
|
}
|
|
|
|
if ( animations[animNum].numFrames == 0 )
|
|
{//we don't use this anim
|
|
//Com_Printf(S_COLOR_YELLOW"WARNING: %s animsounds.cfg: anim %s not used by this model\n", filename, token);
|
|
|
|
// Get offset to frame within sequence
|
|
token = COM_Parse( text_p );
|
|
//get soundstring
|
|
token = COM_Parse( text_p );
|
|
//get lowest value
|
|
token = COM_Parse( text_p );
|
|
//get highest value
|
|
token = COM_Parse( text_p );
|
|
//get probability
|
|
token = COM_Parse( text_p );
|
|
|
|
continue;
|
|
}
|
|
|
|
animSounds->keyFrame = animations[animNum].firstFrame;
|
|
|
|
// Get offset to frame within sequence
|
|
token = COM_Parse( text_p );
|
|
if ( !token )
|
|
{
|
|
break;
|
|
}
|
|
animSounds->keyFrame += atoi( token );
|
|
|
|
//get soundstring
|
|
token = COM_Parse( text_p );
|
|
if ( !token )
|
|
{
|
|
break;
|
|
}
|
|
Q_strncpyz(soundString, token, sizeof(soundString));
|
|
|
|
//get lowest value
|
|
token = COM_Parse( text_p );
|
|
if ( !token )
|
|
{//WARNING! BAD TABLE!
|
|
break;
|
|
}
|
|
lowestVal = atoi( token );
|
|
|
|
//get highest value
|
|
token = COM_Parse( text_p );
|
|
if ( !token )
|
|
{//WARNING! BAD TABLE!
|
|
break;
|
|
}
|
|
highestVal = atoi( token );
|
|
|
|
//Now precache all the sounds
|
|
//NOTE: If we can be assured sequential handles, we can store sound indices
|
|
// instead of strings, unfortunately, if these sounds were previously
|
|
// registered, we cannot be guaranteed sequential indices. Thus an array
|
|
if(lowestVal && highestVal)
|
|
{
|
|
for ( n = lowestVal, num = 0; n <= highestVal && num < MAX_RANDOM_ANIMSOUNDS; n++, num++ )
|
|
{
|
|
animSounds->soundIndex[num] = G_SoundIndex( va( soundString, n ) );//cgi_S_RegisterSound
|
|
}
|
|
animSounds->numRandomAnimSounds = num - 1;
|
|
}
|
|
else
|
|
{
|
|
animSounds->soundIndex[0] = G_SoundIndex( va( soundString ) );//cgi_S_RegisterSound
|
|
#ifndef FINAL_BUILD
|
|
if ( !animSounds->soundIndex[0] )
|
|
{//couldn't register it - file not found
|
|
Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (animsound.cfg %s)!\n", soundString, asb_filename );
|
|
}
|
|
#endif
|
|
animSounds->numRandomAnimSounds = 0;
|
|
}
|
|
|
|
|
|
//get probability
|
|
token = COM_Parse( text_p );
|
|
if ( !token )
|
|
{//WARNING! BAD TABLE!
|
|
break;
|
|
}
|
|
|
|
animSounds->probability = atoi( token );
|
|
++animSounds;
|
|
++*i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_ClearAnimSndCache
|
|
|
|
resets all the soundcache so that a vid restart will recache them
|
|
======================
|
|
*/
|
|
void CG_ClearAnimSndCache( void )
|
|
{
|
|
int i;
|
|
for (i=0; i < level.numKnownAnimFileSets; i++) {
|
|
level.knownAnimFileSets[i].soundsCached = qfalse;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_ParseAnimationSndFile
|
|
|
|
Read a configuration file containing animation sounds
|
|
models/players/kyle/animsounds.cfg, etc
|
|
|
|
This file's presence is not required
|
|
|
|
======================
|
|
*/
|
|
void CG_ParseAnimationSndFile( const char *as_filename, int animFileIndex )
|
|
{
|
|
const char *text_p;
|
|
int len;
|
|
const char *token;
|
|
char text[20000];
|
|
char sfilename[MAX_QPATH];
|
|
fileHandle_t f;
|
|
int i, j, upper_i, lower_i;
|
|
|
|
assert(animFileIndex < MAX_ANIM_FILES);
|
|
animsounds_t *legsAnimSnds = level.knownAnimFileSets[animFileIndex].legsAnimSnds;
|
|
animsounds_t *torsoAnimSnds = level.knownAnimFileSets[animFileIndex].torsoAnimSnds;
|
|
animation_t *animations = level.knownAnimFileSets[animFileIndex].animations;
|
|
|
|
if ( level.knownAnimFileSets[animFileIndex].soundsCached )
|
|
{//already cached this one
|
|
return;
|
|
}
|
|
|
|
//Mark this anim set so that we know we tried to load he sounds, don't care if the load failed
|
|
level.knownAnimFileSets[animFileIndex].soundsCached = qtrue;
|
|
|
|
// Load and parse animSounds.cfg file
|
|
Com_sprintf( sfilename, sizeof( sfilename ), "models/players/%s/animsounds.cfg", as_filename );
|
|
|
|
//initialize anim sound array
|
|
for(i = 0; i < MAX_ANIM_SOUNDS; i++)
|
|
{
|
|
torsoAnimSnds[i].numRandomAnimSounds = 0;
|
|
legsAnimSnds[i].numRandomAnimSounds = 0;
|
|
for(j = 0; j < MAX_RANDOM_ANIMSOUNDS; j++)
|
|
{
|
|
torsoAnimSnds[i].soundIndex[j] = -1;
|
|
legsAnimSnds[i].soundIndex[j] = -1;
|
|
}
|
|
}
|
|
|
|
// load the file
|
|
len = cgi_FS_FOpenFile( sfilename, &f, FS_READ );
|
|
if ( len <= 0 )
|
|
{//no file
|
|
return;
|
|
}
|
|
if ( len >= (int)sizeof( text ) - 1 )
|
|
{
|
|
cgi_FS_FCloseFile( f );
|
|
CG_Printf( "File %s too long\n", sfilename );
|
|
return;
|
|
}
|
|
|
|
cgi_FS_Read( text, len, f );
|
|
text[len] = 0;
|
|
cgi_FS_FCloseFile( f );
|
|
|
|
// parse the text
|
|
text_p = text;
|
|
upper_i =0;
|
|
lower_i =0;
|
|
|
|
// read information for batches of sounds (UPPER or LOWER)
|
|
COM_BeginParseSession();
|
|
while ( 1 )
|
|
{
|
|
// Get base frame of sequence
|
|
token = COM_Parse( &text_p );
|
|
if ( !token || !token[0] )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( !Q_stricmp(token,"UPPERSOUNDS") ) // A batch of upper sounds
|
|
{
|
|
ParseAnimationSndBlock( as_filename, torsoAnimSnds, animations, &upper_i, &text_p );
|
|
}
|
|
|
|
else if ( !Q_stricmp(token,"LOWERSOUNDS") ) // A batch of lower sounds
|
|
{
|
|
ParseAnimationSndBlock( as_filename, legsAnimSnds, animations, &lower_i, &text_p );
|
|
}
|
|
}
|
|
COM_EndParseSession( );
|
|
}
|
|
/*
|
|
===============
|
|
CG_SetLerpFrameAnimation
|
|
===============
|
|
*/
|
|
void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation )
|
|
{
|
|
animation_t *anim;
|
|
|
|
if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS )
|
|
{
|
|
#ifdef FINAL_BUILD
|
|
newAnimation = 0;
|
|
#else
|
|
CG_Error( "Bad animation number: %i", newAnimation );
|
|
#endif
|
|
}
|
|
|
|
lf->animationNumber = newAnimation;
|
|
|
|
if ( !ValidAnimFileIndex( ci->animFileIndex ) )
|
|
{
|
|
#ifdef FINAL_BUILD
|
|
ci->animFileIndex = 0;
|
|
#else
|
|
CG_Error( "Bad animFileIndex: %i", ci->animFileIndex );
|
|
#endif
|
|
}
|
|
|
|
anim = &level.knownAnimFileSets[ci->animFileIndex].animations[ newAnimation ];
|
|
|
|
lf->animation = anim;
|
|
lf->animationTime = lf->frameTime + anim->initialLerp;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_RunLerpFrame
|
|
|
|
Sets cg.snap, cg.oldFrame, and cg.backlerp
|
|
cg.time should be between oldFrameTime and frameTime after exit
|
|
===============
|
|
*/
|
|
qboolean CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float fpsMod, int entNum ) {
|
|
int f, animFrameTime;
|
|
animation_t *anim;
|
|
qboolean newFrame = qfalse;
|
|
|
|
if(fpsMod > 2 || fpsMod < 0.5)
|
|
{//should have been set right
|
|
fpsMod = 1.0f;
|
|
}
|
|
|
|
// debugging tool to get no animations
|
|
if ( cg_animSpeed.integer == 0 )
|
|
{
|
|
lf->oldFrame = lf->frame = lf->backlerp = 0;
|
|
return qfalse;
|
|
}
|
|
|
|
// see if the animation sequence is switching
|
|
//FIXME: allow multiple-frame overlapped lerping between sequences? - Possibly last 3 of last seq and first 3 of next seq?
|
|
if ( newAnimation != lf->animationNumber || !lf->animation )
|
|
{
|
|
CG_SetLerpFrameAnimation( 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;
|
|
|
|
// get the next frame based on the animation
|
|
anim = lf->animation;
|
|
//Do we need to speed up or slow down the anim?
|
|
/*if(fpsMod != 1.0)
|
|
{//Note! despite it's name, a higher fpsMod slows down the anim, a lower one speeds it up
|
|
animFrameTime = ceil(lf->frameTime * fpsMod);
|
|
}
|
|
else*/
|
|
{
|
|
animFrameTime = fabs((double)anim->frameLerp);
|
|
|
|
//special hack for player to ensure quick weapon change
|
|
if ( entNum == 0 )
|
|
{
|
|
if ( lf->animationNumber == TORSO_DROPWEAP1 || lf->animationNumber == TORSO_RAISEWEAP1 )
|
|
{
|
|
animFrameTime = 50;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( cg.time < lf->animationTime )
|
|
{
|
|
lf->frameTime = lf->animationTime; // initial lerp
|
|
}
|
|
else
|
|
{
|
|
lf->frameTime = lf->oldFrameTime + animFrameTime;
|
|
}
|
|
|
|
f = ( lf->frameTime - lf->animationTime ) / animFrameTime;
|
|
if ( f >= anim->numFrames )
|
|
{//Reached the end of the anim
|
|
//FIXME: Need to set a flag here to TASK_COMPLETE
|
|
f -= anim->numFrames;
|
|
if ( anim->loopFrames != -1 ) //Before 0 meant no loop
|
|
{
|
|
if(anim->numFrames - anim->loopFrames == 0)
|
|
{
|
|
f %= anim->numFrames;
|
|
}
|
|
else
|
|
{
|
|
f %= (anim->numFrames - anim->loopFrames);
|
|
}
|
|
f += 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->frameLerp < 0 )
|
|
{
|
|
lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
|
|
}
|
|
else
|
|
{
|
|
lf->frame = anim->firstFrame + f;
|
|
}
|
|
|
|
if ( cg.time > lf->frameTime )
|
|
{
|
|
lf->frameTime = cg.time;
|
|
}
|
|
|
|
newFrame = qtrue;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
return newFrame;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_ClearLerpFrame
|
|
===============
|
|
*/
|
|
void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber )
|
|
{
|
|
lf->frameTime = lf->oldFrameTime = cg.time;
|
|
CG_SetLerpFrameAnimation( ci, lf, animationNumber );
|
|
if ( lf->animation->frameLerp < 0 )
|
|
{//Plays backwards
|
|
lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames);
|
|
}
|
|
else
|
|
{
|
|
lf->oldFrame = lf->frame = lf->animation->firstFrame;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerAnimation
|
|
===============
|
|
*/
|
|
void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
|
|
int *torsoOld, int *torso, float *torsoBackLerp ) {
|
|
clientInfo_t *ci;
|
|
int legsAnim;
|
|
int legsTurnAnim = -1;
|
|
qboolean newLegsFrame = qfalse;
|
|
qboolean newTorsoFrame = qfalse;
|
|
|
|
if ( cg_noPlayerAnims.integer ) {
|
|
*legsOld = *legs = *torsoOld = *torso = 0;
|
|
return;
|
|
}
|
|
|
|
ci = ¢->gent->client->clientInfo;
|
|
//Changed this from cent->currentState.legsAnim to cent->gent->client->ps.legsAnim because it was screwing up our timers when we've just changed anims while turning
|
|
legsAnim = cent->gent->client->ps.legsAnim;
|
|
|
|
// do the shuffle turn frames locally (MAN this is an Fugly-ass hack!)
|
|
|
|
if ( cent->pe.legs.yawing )
|
|
{
|
|
legsTurnAnim = PM_GetTurnAnim( cent->gent, legsAnim );
|
|
}
|
|
|
|
if ( legsTurnAnim != -1 )
|
|
{
|
|
newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, legsTurnAnim, cent->gent->client->renderInfo.legsFpsMod, cent->gent->s.number );
|
|
//This line doesn't seem to serve any useful purpose, rather it
|
|
//breaks things since any task waiting for a lower anim to complete
|
|
//never will finish if this happens!!!
|
|
//cent->gent->client->ps.legsAnimTimer = 0;
|
|
}
|
|
else
|
|
{
|
|
newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, legsAnim, cent->gent->client->renderInfo.legsFpsMod, cent->gent->s.number);
|
|
}
|
|
|
|
*legsOld = cent->pe.legs.oldFrame;
|
|
*legs = cent->pe.legs.frame;
|
|
*legsBackLerp = cent->pe.legs.backlerp;
|
|
|
|
if( newLegsFrame )
|
|
{
|
|
if ( ValidAnimFileIndex( ci->animFileIndex ) )
|
|
{
|
|
CG_PlayerAnimSounds( ci->animFileIndex, qfalse, cent->pe.legs.frame, cent->pe.legs.frame, cent->currentState.number );
|
|
}
|
|
}
|
|
|
|
newTorsoFrame = CG_RunLerpFrame( ci, ¢->pe.torso, cent->gent->client->ps.torsoAnim, cent->gent->client->renderInfo.torsoFpsMod, cent->gent->s.number );
|
|
|
|
*torsoOld = cent->pe.torso.oldFrame;
|
|
*torso = cent->pe.torso.frame;
|
|
*torsoBackLerp = cent->pe.torso.backlerp;
|
|
|
|
if( newTorsoFrame )
|
|
{
|
|
if ( ValidAnimFileIndex( ci->animFileIndex ) )
|
|
{
|
|
CG_PlayerAnimSounds(ci->animFileIndex, qtrue, cent->pe.torso.frame, cent->pe.torso.frame, cent->currentState.number );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
void CG_PlayerAnimSounds( int animFileIndex, qboolean torso, int oldFrame, int frame, const vec3_t org, int entNum )
|
|
|
|
play any keyframed sounds - only when start a new frame
|
|
This func is called once for legs and once for torso
|
|
*/
|
|
extern int PM_LegsAnimForFrame( gentity_t *ent, int legsFrame );
|
|
extern int PM_TorsoAnimForFrame( gentity_t *ent, int torsoFrame );
|
|
void CG_PlayerAnimSounds( int animFileIndex, qboolean torso, int oldFrame, int frame, int entNum )
|
|
{
|
|
int i;
|
|
int holdSnd = -1;
|
|
int firstFrame = 0, lastFrame = 0;
|
|
qboolean playSound = qfalse, inSameAnim = qfalse, loopAnim = qfalse, match = qfalse, animBackward = qfalse;
|
|
animsounds_t *animSounds = NULL;
|
|
|
|
if ( torso )
|
|
{
|
|
animSounds = level.knownAnimFileSets[animFileIndex].torsoAnimSnds;
|
|
}
|
|
else
|
|
{
|
|
animSounds = level.knownAnimFileSets[animFileIndex].legsAnimSnds;
|
|
}
|
|
if ( fabs((double)oldFrame-frame) > 1 && cg_reliableAnimSounds.integer )
|
|
{//given a range, see if keyFrame falls in that range
|
|
int oldAnim, anim;
|
|
if ( torso )
|
|
{
|
|
if ( cg_reliableAnimSounds.integer > 1 )
|
|
{//more precise, slower
|
|
oldAnim = PM_TorsoAnimForFrame( &g_entities[entNum], oldFrame );
|
|
anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame );
|
|
}
|
|
else
|
|
{//less precise, but faster
|
|
oldAnim = cg_entities[entNum].currentState.torsoAnim;
|
|
anim = cg_entities[entNum].nextState.torsoAnim;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( cg_reliableAnimSounds.integer > 1 )
|
|
{//more precise, slower
|
|
oldAnim = PM_LegsAnimForFrame( &g_entities[entNum], oldFrame );
|
|
anim = PM_LegsAnimForFrame( &g_entities[entNum], frame );
|
|
}
|
|
else
|
|
{//less precise, but faster
|
|
oldAnim = cg_entities[entNum].currentState.legsAnim;
|
|
anim = cg_entities[entNum].nextState.legsAnim;
|
|
}
|
|
}
|
|
if ( anim != oldAnim )
|
|
{//not in same anim
|
|
inSameAnim = qfalse;
|
|
//FIXME: we *could* see if the oldFrame was *just about* to play the keyframed sound...
|
|
}
|
|
else
|
|
{//still in same anim, check for looping anim
|
|
inSameAnim = qtrue;
|
|
animation_t *animation = &level.knownAnimFileSets[animFileIndex].animations[anim];
|
|
animBackward = (qboolean)(animation->frameLerp < 0);
|
|
if ( animation->loopFrames != -1 )
|
|
{//a looping anim!
|
|
loopAnim = qtrue;
|
|
firstFrame = animation->firstFrame;
|
|
lastFrame = animation->firstFrame+animation->numFrames;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( entNum == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
|
|
{//player in first person view does not play any keyframed sounds
|
|
return;
|
|
}
|
|
|
|
// Check for anim sound
|
|
for (i=0;i<MAX_ANIM_SOUNDS;++i)
|
|
{
|
|
if (animSounds[i].soundIndex[0] == -1) // No sounds in array
|
|
{
|
|
break;
|
|
}
|
|
|
|
match = qfalse;
|
|
if ( animSounds[i].keyFrame == frame )
|
|
{//exact match
|
|
match = qtrue;
|
|
}
|
|
else if ( fabs((double)oldFrame-frame) > 1 && cg_reliableAnimSounds.integer )
|
|
{//given a range, see if keyFrame falls in that range
|
|
if ( inSameAnim )
|
|
{//if changed anims altogether, sorry, the sound is lost
|
|
if ( fabs((double)oldFrame-animSounds[i].keyFrame) <= 3
|
|
|| fabs((double)frame-animSounds[i].keyFrame) <= 3 )
|
|
{//must be at least close to the keyframe
|
|
if ( animBackward )
|
|
{//animation plays backwards
|
|
if ( oldFrame > animSounds[i].keyFrame && frame < animSounds[i].keyFrame )
|
|
{//old to new passed through keyframe
|
|
match = qtrue;
|
|
}
|
|
else if ( loopAnim )
|
|
{//hmm, didn't pass through it linearally, see if we looped
|
|
if ( animSounds[i].keyFrame >= firstFrame && animSounds[i].keyFrame < lastFrame )
|
|
{//keyframe is in this anim
|
|
if ( oldFrame > animSounds[i].keyFrame
|
|
&& frame > oldFrame )
|
|
{//old to new passed through keyframe
|
|
match = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//anim plays forwards
|
|
if ( oldFrame < animSounds[i].keyFrame && frame > animSounds[i].keyFrame )
|
|
{//old to new passed through keyframe
|
|
match = qtrue;
|
|
}
|
|
else if ( loopAnim )
|
|
{//hmm, didn't pass through it linearally, see if we looped
|
|
if ( animSounds[i].keyFrame >= firstFrame && animSounds[i].keyFrame < lastFrame )
|
|
{//keyframe is in this anim
|
|
if ( oldFrame < animSounds[i].keyFrame
|
|
&& frame < oldFrame )
|
|
{//old to new passed through keyframe
|
|
match = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( match )
|
|
{
|
|
// are there variations on the sound?
|
|
holdSnd = animSounds[i].soundIndex[ Q_irand( 0, animSounds[i].numRandomAnimSounds ) ];
|
|
|
|
// Determine probability of playing sound
|
|
if (!animSounds[i].probability) // 100%
|
|
{
|
|
playSound = qtrue;
|
|
}
|
|
else if (animSounds[i].probability > Q_irand(0, 99) )
|
|
{
|
|
playSound = qtrue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Play sound
|
|
if (holdSnd != -1 && playSound)
|
|
{
|
|
if (holdSnd != 0) // 0 = default sound, ie file was missing
|
|
{
|
|
//FIXME: allow customSounds in here *...
|
|
if ( cgs.sound_precache[ holdSnd ] )
|
|
{
|
|
cgi_S_StartSound( NULL, entNum, CHAN_AUTO, cgs.sound_precache[holdSnd ] );
|
|
}
|
|
else
|
|
{
|
|
holdSnd = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void CGG2_AnimSounds( centity_t *cent )
|
|
{
|
|
if ( !cent || !cent->gent || !cent->gent->client)
|
|
{
|
|
return;
|
|
}
|
|
assert(cent->gent->playerModel>=0&¢->gent->playerModel<cent->gent->ghoul2.size());
|
|
if ( ValidAnimFileIndex( cent->gent->client->clientInfo.animFileIndex ) )
|
|
{
|
|
int junk, curFrame=0;
|
|
float currentFrame=0, animSpeed;
|
|
|
|
if (cent->gent->rootBone>=0&&gi.G2API_GetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone, cg.time, ¤tFrame, &junk, &junk, &junk, &animSpeed, cgs.model_draw ))
|
|
{
|
|
// the above may have failed, not sure what to do about it, current frame will be zero in that case
|
|
curFrame = floor( currentFrame );
|
|
}
|
|
if ( curFrame != cent->gent->client->renderInfo.legsFrame )
|
|
{
|
|
CG_PlayerAnimSounds( cent->gent->client->clientInfo.animFileIndex, qfalse, cent->gent->client->renderInfo.legsFrame, curFrame, cent->currentState.clientNum );
|
|
}
|
|
cent->gent->client->renderInfo.legsFrame = curFrame;
|
|
cent->pe.legs.frame = curFrame;
|
|
|
|
if (cent->gent->lowerLumbarBone>=0&& gi.G2API_GetBoneAnimIndex(¢->gent->ghoul2[cent->gent->playerModel], cent->gent->lowerLumbarBone, cg.time, ¤tFrame, &junk, &junk, &junk, &animSpeed, cgs.model_draw ) )
|
|
{
|
|
curFrame = floor( currentFrame );
|
|
}
|
|
if ( curFrame != cent->gent->client->renderInfo.torsoFrame )
|
|
{
|
|
CG_PlayerAnimSounds( cent->gent->client->clientInfo.animFileIndex, qtrue, cent->gent->client->renderInfo.torsoFrame, curFrame, cent->currentState.clientNum );
|
|
}
|
|
cent->gent->client->renderInfo.torsoFrame = curFrame;
|
|
cent->pe.torso.frame = curFrame;
|
|
}
|
|
}
|
|
/*
|
|
=============================================================================
|
|
|
|
PLAYER ANGLES
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
/*
|
|
==================
|
|
CG_UpdateAngleClamp
|
|
Turn curAngle toward destAngle at angleSpeed, but stay within clampMin and Max
|
|
==================
|
|
*/
|
|
void CG_UpdateAngleClamp( float destAngle, float clampMin, float clampMax, float angleSpeed, float *curAngle, float normalAngle)
|
|
{
|
|
float swing;
|
|
float move;
|
|
float scale;
|
|
float actualSpeed;
|
|
|
|
swing = AngleSubtract( destAngle, *curAngle );
|
|
|
|
if(swing == 0)
|
|
{//Don't have to turn
|
|
return;
|
|
}
|
|
|
|
// modify the angleSpeed depending on the delta
|
|
// so it doesn't seem so linear
|
|
scale = fabs( swing );
|
|
if (swing > 0)
|
|
{
|
|
if ( swing < clampMax * 0.25 )
|
|
{//Pretty small way to go
|
|
scale = 0.25;
|
|
}
|
|
else if ( swing > clampMax * 2.0 )
|
|
{//Way out of our range
|
|
scale = 2.0;
|
|
}
|
|
else
|
|
{//Scale it smoothly
|
|
scale = swing/clampMax;
|
|
}
|
|
}
|
|
else// if (swing < 0)
|
|
{
|
|
if ( swing > clampMin * 0.25 )
|
|
{//Pretty small way to go
|
|
scale = 0.5;
|
|
}
|
|
else if ( swing < clampMin * 2.0 )
|
|
{//Way out of our range
|
|
scale = 2.0;
|
|
}
|
|
else
|
|
{//Scale it smoothly
|
|
scale = swing/clampMin;
|
|
}
|
|
}
|
|
|
|
actualSpeed = scale * angleSpeed;
|
|
// swing towards the destination angle
|
|
if ( swing >= 0 )
|
|
{
|
|
move = cg.frametime * actualSpeed;
|
|
if ( move >= swing )
|
|
{//our turnspeed is so fast, no need to swing, just match
|
|
*curAngle = destAngle;
|
|
}
|
|
else
|
|
{
|
|
*curAngle = AngleNormalize360( *curAngle + move );
|
|
}
|
|
}
|
|
else if ( swing < 0 )
|
|
{
|
|
move = cg.frametime * -actualSpeed;
|
|
if ( move <= swing )
|
|
{//our turnspeed is so fast, no need to swing, just match
|
|
*curAngle = destAngle;
|
|
}
|
|
else
|
|
{
|
|
*curAngle = AngleNormalize180( *curAngle + move );
|
|
}
|
|
}
|
|
|
|
swing = AngleSubtract( *curAngle, normalAngle );
|
|
|
|
// clamp to no more than normalAngle + tolerance
|
|
if ( swing > clampMax )
|
|
{
|
|
*curAngle = AngleNormalize180( normalAngle + clampMax );
|
|
}
|
|
else if ( swing < clampMin )
|
|
{
|
|
*curAngle = AngleNormalize180( normalAngle + clampMin );
|
|
}
|
|
}
|
|
/*
|
|
==================
|
|
CG_SwingAngles
|
|
|
|
If the body is not locked OR if the upper part is trying to swing beyond it's
|
|
range, turn the lower body part to catch up.
|
|
|
|
Parms: desired angle, (Our eventual goal angle
|
|
min swing tolerance,(Lower angle value threshold at which to start turning)
|
|
max swing tolerance,(Upper angle value threshold at which to start turning)
|
|
min clamp tolerance,(Lower angle value threshold to clamp output angle to)
|
|
max clamp tolerance,(Upper angle value threshold to clamp output angle to)
|
|
angle speed, (How fast to turn)
|
|
current angle, (Current angle to modify)
|
|
locked mode (Don't turn unless you exceed the swing/clamp tolerance)
|
|
==================
|
|
*/
|
|
void CG_SwingAngles( float destAngle,
|
|
float swingTolMin, float swingTolMax,
|
|
float clampMin, float clampMax,
|
|
float angleSpeed, float *curAngle,
|
|
qboolean *turning )
|
|
{
|
|
float swing;
|
|
float move;
|
|
float scale;
|
|
|
|
swing = AngleSubtract( destAngle, *curAngle );
|
|
|
|
if(swing == 0)
|
|
{//Don't have to turn
|
|
*turning = qfalse;
|
|
}
|
|
else
|
|
{
|
|
*turning = qtrue;
|
|
}
|
|
|
|
//If we're not turning, then we're done
|
|
if ( *turning == qfalse)
|
|
return;
|
|
|
|
// modify the angleSpeed depending on the delta
|
|
// so it doesn't seem so linear
|
|
scale = fabs( swing );
|
|
|
|
if (swing > 0)
|
|
{
|
|
if ( clampMax <= 0 )
|
|
{
|
|
*curAngle = destAngle;
|
|
return;
|
|
}
|
|
|
|
if ( swing < swingTolMax * 0.5 )
|
|
{//Pretty small way to go
|
|
scale = 0.5;
|
|
}
|
|
else if ( scale < swingTolMax )
|
|
{//More than halfway to go
|
|
scale = 1.0;
|
|
}
|
|
else
|
|
{//Way out of our range
|
|
scale = 2.0;
|
|
}
|
|
}
|
|
else// if (swing < 0)
|
|
{
|
|
if ( clampMin >= 0 )
|
|
{
|
|
*curAngle = destAngle;
|
|
return;
|
|
}
|
|
|
|
if ( swing > swingTolMin * 0.5 )
|
|
{//Pretty small way to go
|
|
scale = 0.5;
|
|
}
|
|
else if ( scale > swingTolMin )
|
|
{//More than halfway to go
|
|
scale = 1.0;
|
|
}
|
|
else
|
|
{//Way out of our range
|
|
scale = 2.0;
|
|
}
|
|
}
|
|
|
|
// swing towards the destination angle
|
|
if ( swing >= 0 )
|
|
{
|
|
move = cg.frametime * scale * angleSpeed;
|
|
if ( move >= swing )
|
|
{//our turnspeed is so fast, no need to swing, just match
|
|
move = swing;
|
|
}
|
|
*curAngle = AngleNormalize360( *curAngle + move );
|
|
}
|
|
else if ( swing < 0 )
|
|
{
|
|
move = cg.frametime * scale * -angleSpeed;
|
|
if ( move <= swing )
|
|
{//our turnspeed is so fast, no need to swing, just match
|
|
move = swing;
|
|
}
|
|
*curAngle = AngleNormalize360( *curAngle + move );
|
|
}
|
|
|
|
|
|
// clamp to no more than tolerance
|
|
if ( swing > clampMax )
|
|
{
|
|
*curAngle = AngleNormalize360( destAngle - (clampMax - 1) );
|
|
}
|
|
else if ( swing < clampMin )
|
|
{
|
|
*curAngle = AngleNormalize360( destAngle + (-clampMin - 1) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_AddPainTwitch
|
|
=================
|
|
*/
|
|
/*
|
|
static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) {
|
|
int t;
|
|
float f;
|
|
|
|
t = cg.time - cent->pe.painTime;
|
|
if ( t >= PAIN_TWITCH_TIME ) {
|
|
return;
|
|
}
|
|
|
|
f = 1.0 - (float)t / PAIN_TWITCH_TIME;
|
|
|
|
if ( cent->pe.painDirection ) {
|
|
torsoAngles[ROLL] += 20 * f;
|
|
} else {
|
|
torsoAngles[ROLL] -= 20 * f;
|
|
}
|
|
}
|
|
*/
|
|
//FIXME: Don't do this, use tag_eye instead?
|
|
//--------------------------------------------------------------
|
|
/*
|
|
float CG_EyePointOfsForRace[RACE_HOLOGRAM+1][2] =
|
|
{
|
|
0, 0,//RACE_NONE = 0,
|
|
4, 8,//RACE_HUMAN,
|
|
4, 8,//RACE_BORG,
|
|
4, 8,//RACE_KLINGON,
|
|
4, 8,//RACE_HIROGEN,
|
|
4, 8,//RACE_MALON,
|
|
0, 0,//RACE_STASIS,
|
|
0, 0,//RACE_8472,
|
|
0, 0,//RACE_BOT,
|
|
8, 0,//RACE_HARVESTER,
|
|
6, -6,//RACE_REAVER,
|
|
4, 0,//RACE_AVATAR,
|
|
4, 0,//RACE_PARASITE,
|
|
4, 8,//RACE_VULCAN,
|
|
4, 8,//RACE_BETAZOID,
|
|
4, 8,//RACE_BOLIAN,
|
|
4, 8,//RACE_TALAXIAN,
|
|
4, 8,//RACE_BAJORAN,
|
|
4, 8//RACE_HOLOGRAM
|
|
};
|
|
*/
|
|
#define LOOK_DEFAULT_SPEED 0.15f
|
|
#define LOOK_TALKING_SPEED 0.15f
|
|
|
|
static qboolean CG_CheckLookTarget( centity_t *cent, vec3_t lookAngles, float *lookingSpeed )
|
|
{
|
|
if ( !cent->gent->ghoul2.size() )
|
|
{
|
|
if ( !cent->gent->client->clientInfo.torsoModel || !cent->gent->client->clientInfo.headModel )
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
//FIXME: also clamp the lookAngles based on the clamp + the existing difference between
|
|
// headAngles and torsoAngles? But often the tag_torso is straight but the torso itself
|
|
// is deformed to not face straight... sigh...
|
|
|
|
//Now calc head angle to lookTarget, if any
|
|
if ( cent->gent->client->renderInfo.lookTarget >= 0 && cent->gent->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
|
|
{
|
|
vec3_t lookDir, lookOrg = { 0.0f }, eyeOrg;
|
|
if ( cent->gent->client->renderInfo.lookMode == LM_ENT )
|
|
{
|
|
centity_t *lookCent = &cg_entities[cent->gent->client->renderInfo.lookTarget];
|
|
if ( lookCent && lookCent->gent )
|
|
{
|
|
if ( lookCent->gent != cent->gent->enemy )
|
|
{//We turn heads faster than headbob speed, but not as fast as if watching an enemy
|
|
*lookingSpeed = LOOK_DEFAULT_SPEED;
|
|
}
|
|
|
|
//FIXME: Ignore small deltas from current angles so we don't bob our head in synch with theirs?
|
|
|
|
if ( cent->gent->client->renderInfo.lookTarget == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
|
|
{//Special case- use cg.refdef.vieworg if looking at player and not in third person view
|
|
VectorCopy( cg.refdef.vieworg, lookOrg );
|
|
}
|
|
else if ( lookCent->gent->client )
|
|
{
|
|
VectorCopy( lookCent->gent->client->renderInfo.eyePoint, lookOrg );
|
|
}
|
|
else if ( lookCent->gent->s.pos.trType == TR_INTERPOLATE )
|
|
{
|
|
VectorCopy( lookCent->lerpOrigin, lookOrg );
|
|
}
|
|
else if ( lookCent->gent->inuse && !VectorCompare( lookCent->gent->currentOrigin, vec3_origin ) )
|
|
{
|
|
VectorCopy( lookCent->gent->currentOrigin, lookOrg );
|
|
}
|
|
else
|
|
{//at origin of world
|
|
return qfalse;
|
|
}
|
|
//Look in dir of lookTarget
|
|
}
|
|
}
|
|
else if ( cent->gent->client->renderInfo.lookMode == LM_INTEREST && cent->gent->client->renderInfo.lookTarget > -1 && cent->gent->client->renderInfo.lookTarget < MAX_INTEREST_POINTS )
|
|
{
|
|
VectorCopy( level.interestPoints[cent->gent->client->renderInfo.lookTarget].origin, lookOrg );
|
|
}
|
|
else
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
VectorCopy( cent->gent->client->renderInfo.eyePoint, eyeOrg );
|
|
|
|
VectorSubtract( lookOrg, eyeOrg, lookDir );
|
|
#if 1
|
|
vectoangles( lookDir, lookAngles );
|
|
#else
|
|
//FIXME: get the angle of the head tag and account for that when finding the lookAngles-
|
|
// so if they're lying on their back we get an accurate lookAngle...
|
|
vec3_t headDirs[3];
|
|
vec3_t finalDir;
|
|
|
|
AnglesToAxis( cent->gent->client->renderInfo.headAngles, headDirs );
|
|
VectorRotate( lookDir, headDirs, finalDir );
|
|
vectoangles( finalDir, lookAngles );
|
|
#endif
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
lookAngles[i] = AngleNormalize180( lookAngles[i] );
|
|
cent->gent->client->renderInfo.eyeAngles[i] = AngleNormalize180( cent->gent->client->renderInfo.eyeAngles[i] );
|
|
}
|
|
AnglesSubtract( lookAngles, cent->gent->client->renderInfo.eyeAngles, lookAngles );
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_AddHeadBob
|
|
=================
|
|
*/
|
|
static qboolean CG_AddHeadBob( centity_t *cent, vec3_t addTo )
|
|
{
|
|
renderInfo_t *renderInfo = ¢->gent->client->renderInfo;
|
|
const int volume = gi.VoiceVolume[cent->gent->s.clientNum];
|
|
const int volChange = volume - renderInfo->lastVoiceVolume;//was *3 because voice fromLA was too low
|
|
int i;
|
|
|
|
renderInfo->lastVoiceVolume = volume;
|
|
|
|
if ( !volume )
|
|
{
|
|
// Not talking, set our target to be the normal head position
|
|
VectorClear( renderInfo->targetHeadBobAngles );
|
|
|
|
if ( VectorLengthSquared( renderInfo->headBobAngles ) < 1.0f )
|
|
{
|
|
// We are close enough to being back to our normal head position, so we are done for now
|
|
return qfalse;
|
|
}
|
|
}
|
|
else if ( volChange > 2 )
|
|
{
|
|
// a big positive change in volume
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
// Move our head angle target a bit
|
|
renderInfo->targetHeadBobAngles[i] += Q_flrand( -1.0 * volChange, 1.0 * volChange );
|
|
|
|
// Clamp so we don't get too out of hand
|
|
if ( renderInfo->targetHeadBobAngles[i] > 7.0f )
|
|
renderInfo->targetHeadBobAngles[i] = 7.0f;
|
|
|
|
if ( renderInfo->targetHeadBobAngles[i] < -7.0f )
|
|
renderInfo->targetHeadBobAngles[i] = -7.0f;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
// Always try to move head angles towards our target
|
|
renderInfo->headBobAngles[i] += ( renderInfo->targetHeadBobAngles[i] - renderInfo->headBobAngles[i] ) * ( cg.frametime / 150.0f );
|
|
if ( addTo )
|
|
{
|
|
addTo[i] = AngleNormalize180( addTo[i] + AngleNormalize180( renderInfo->headBobAngles[i] ) );
|
|
}
|
|
}
|
|
|
|
// We aren't back to our normal position yet, so we still have to apply headBobAngles
|
|
return qtrue;
|
|
}
|
|
|
|
extern float vectoyaw( const vec3_t vec );
|
|
qboolean CG_PlayerLegsYawFromMovement( centity_t *cent, const vec3_t velocity, float *yaw, float fwdAngle, float swingTolMin, float swingTolMax, qboolean alwaysFace )
|
|
{
|
|
float newAddAngle, angleDiff, turnRate = 10, addAngle = 0;
|
|
|
|
//figure out what the offset, if any, should be
|
|
if ( velocity[0] || velocity[1] )
|
|
{
|
|
float moveYaw;
|
|
moveYaw = vectoyaw( velocity );
|
|
addAngle = AngleDelta( cent->lerpAngles[YAW], moveYaw )*-1;
|
|
if ( addAngle > 150 || addAngle < -150 )
|
|
{
|
|
addAngle = 0;
|
|
}
|
|
else
|
|
{
|
|
//FIXME: use actual swing/clamp tolerances
|
|
if ( addAngle > swingTolMax )
|
|
{
|
|
addAngle = swingTolMax;
|
|
}
|
|
else if ( addAngle < swingTolMin )
|
|
{
|
|
addAngle = swingTolMin;
|
|
}
|
|
if ( cent->gent->client->ps.pm_flags&PMF_BACKWARDS_RUN )
|
|
{
|
|
addAngle *= -1;
|
|
}
|
|
turnRate = 5;
|
|
}
|
|
}
|
|
else if ( !alwaysFace )
|
|
{
|
|
return qfalse;
|
|
}
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->ps.forcePowersActive & (1 << FP_SPEED) )
|
|
{//using force speed
|
|
//scale up the turning speed
|
|
turnRate /= cg_timescale.value;
|
|
}
|
|
//lerp the legs angle to the new angle
|
|
angleDiff = AngleDelta( cent->pe.legs.yawAngle, (*yaw+addAngle) );
|
|
newAddAngle = angleDiff*cg.frameInterpolation*-1;
|
|
if ( fabs(newAddAngle) > fabs(angleDiff) )
|
|
{
|
|
newAddAngle = angleDiff*-1;
|
|
}
|
|
if ( newAddAngle > turnRate )
|
|
{
|
|
newAddAngle = turnRate;
|
|
}
|
|
else if ( newAddAngle < -turnRate )
|
|
{
|
|
newAddAngle = -turnRate;
|
|
}
|
|
*yaw = cent->pe.legs.yawAngle + newAddAngle;
|
|
//Now clamp
|
|
angleDiff = AngleDelta( fwdAngle, *yaw );
|
|
if ( angleDiff > swingTolMax )
|
|
{
|
|
*yaw = fwdAngle - swingTolMax;
|
|
}
|
|
else if ( angleDiff < swingTolMin )
|
|
{
|
|
*yaw = fwdAngle - swingTolMin;
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
void CG_ATSTLegsYaw( centity_t *cent, vec3_t trailingLegsAngles )
|
|
{
|
|
|
|
float ATSTLegsYaw = cent->lerpAngles[YAW];
|
|
|
|
CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &ATSTLegsYaw, cent->lerpAngles[YAW], -60, 60, qtrue );
|
|
|
|
float legAngleDiff = AngleNormalize180(ATSTLegsYaw) - AngleNormalize180(cent->pe.legs.yawAngle);
|
|
int legsAnim = cent->currentState.legsAnim;
|
|
qboolean moving = (qboolean)(!VectorCompare(cent->gent->client->ps.velocity, vec3_origin));
|
|
if ( moving || legsAnim == BOTH_TURN_LEFT1 || legsAnim == BOTH_TURN_RIGHT1 || fabs(legAngleDiff) > 45 )
|
|
{//moving or turning or beyond the turn allowance
|
|
if ( legsAnim == BOTH_STAND1 && !moving )
|
|
{//standing
|
|
if ( legAngleDiff > 0 )
|
|
{
|
|
NPC_SetAnim( cent->gent, SETANIM_LEGS, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
}
|
|
else
|
|
{
|
|
NPC_SetAnim( cent->gent, SETANIM_LEGS, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
}
|
|
VectorSet( trailingLegsAngles, 0, cent->pe.legs.yawAngle, 0 );
|
|
cent->gent->client->renderInfo.legsYaw = trailingLegsAngles[YAW];
|
|
}
|
|
else if ( legsAnim == BOTH_TURN_LEFT1 || legsAnim == BOTH_TURN_RIGHT1 )
|
|
{//turning
|
|
legAngleDiff = AngleSubtract( ATSTLegsYaw, cent->gent->client->renderInfo.legsYaw );
|
|
float add = 0;
|
|
if ( legAngleDiff > 50 )
|
|
{
|
|
cent->pe.legs.yawAngle += legAngleDiff - 50;
|
|
}
|
|
else if ( legAngleDiff < -50 )
|
|
{
|
|
cent->pe.legs.yawAngle += legAngleDiff + 50;
|
|
}
|
|
float animLength = PM_AnimLength( cent->gent->client->clientInfo.animFileIndex, (animNumber_t)legsAnim );
|
|
legAngleDiff *= ( animLength - cent->gent->client->ps.legsAnimTimer)/animLength;
|
|
VectorSet( trailingLegsAngles, 0, cent->pe.legs.yawAngle+legAngleDiff+add, 0 );
|
|
if ( !cent->gent->client->ps.legsAnimTimer )
|
|
{//FIXME: if start turning in the middle of this, our legs pop back to the old cent->pe.legs.yawAngle...
|
|
cent->gent->client->renderInfo.legsYaw = trailingLegsAngles[YAW];
|
|
}
|
|
}
|
|
else
|
|
{//moving
|
|
legAngleDiff = AngleSubtract( ATSTLegsYaw, cent->pe.legs.yawAngle );
|
|
//FIXME: framerate dependant!!!
|
|
if ( legAngleDiff > 50 )
|
|
{
|
|
legAngleDiff -= 50;
|
|
}
|
|
else if ( legAngleDiff > 5 )
|
|
{
|
|
legAngleDiff = 5;
|
|
}
|
|
else if ( legAngleDiff < -50 )
|
|
{
|
|
legAngleDiff += 50;
|
|
}
|
|
else if ( legAngleDiff < -5 )
|
|
{
|
|
legAngleDiff = -5;
|
|
}
|
|
legAngleDiff *= cg.frameInterpolation;
|
|
VectorSet( trailingLegsAngles, 0, AngleNormalize180(cent->pe.legs.yawAngle + legAngleDiff), 0 );
|
|
cent->gent->client->renderInfo.legsYaw = trailingLegsAngles[YAW];
|
|
}
|
|
cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = trailingLegsAngles[YAW];
|
|
cent->pe.legs.yawing = qtrue;
|
|
}
|
|
else
|
|
{
|
|
VectorSet( trailingLegsAngles, 0, cent->pe.legs.yawAngle, 0 );
|
|
cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = trailingLegsAngles[YAW];
|
|
cent->pe.legs.yawing = qfalse;
|
|
}
|
|
return;
|
|
}
|
|
|
|
extern qboolean PM_FlippingAnim( int anim );
|
|
extern qboolean PM_SpinningSaberAnim( int anim );
|
|
void CG_G2ClientSpineAngles( centity_t *cent, vec3_t viewAngles, const vec3_t angles, vec3_t thoracicAngles, vec3_t ulAngles, vec3_t llAngles )
|
|
{
|
|
cent->pe.torso.pitchAngle = viewAngles[PITCH];
|
|
viewAngles[YAW] = AngleDelta( cent->lerpAngles[YAW], angles[YAW] );
|
|
cent->pe.torso.yawAngle = viewAngles[YAW];
|
|
|
|
if ( cg_motionBoneComp.integer
|
|
&& !PM_FlippingAnim( cent->currentState.legsAnim )
|
|
&& !PM_SpinningSaberAnim( cent->currentState.legsAnim )
|
|
&& !PM_SpinningSaberAnim( cent->currentState.torsoAnim )
|
|
&& cent->currentState.legsAnim != cent->currentState.torsoAnim )//NOTE: presumes your legs & torso are on the same frame, though they *should* be because PM_SetAnimFinal tries to keep them in synch
|
|
{//FIXME: no need to do this if legs and torso on are same frame
|
|
//adjust for motion offset
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t motionFwd, motionAngles;
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->motionBolt, &boltMatrix, vec3_origin, cent->lerpOrigin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, motionFwd );
|
|
vectoangles( motionFwd, motionAngles );
|
|
if ( cg_motionBoneComp.integer > 1 )
|
|
{//do roll, too
|
|
vec3_t motionRt, tempAng;
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, motionRt );
|
|
vectoangles( motionRt, tempAng );
|
|
motionAngles[ROLL] = -tempAng[PITCH];
|
|
}
|
|
|
|
for ( int ang = 0; ang < 3; ang++ )
|
|
{
|
|
viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( motionAngles[ang] ) );
|
|
}
|
|
}
|
|
//distribute the angles differently up the spine
|
|
//NOTE: each of these distributions must add up to 1.0f
|
|
thoracicAngles[PITCH] = viewAngles[PITCH]*0.20f;
|
|
llAngles[PITCH] = viewAngles[PITCH]*0.40f;
|
|
ulAngles[PITCH] = viewAngles[PITCH]*0.40f;
|
|
|
|
thoracicAngles[YAW] = viewAngles[YAW]*0.20f;
|
|
ulAngles[YAW] = viewAngles[YAW]*0.35f;
|
|
llAngles[YAW] = viewAngles[YAW]*0.45f;
|
|
|
|
thoracicAngles[ROLL] = viewAngles[ROLL]*0.20f;
|
|
ulAngles[ROLL] = viewAngles[ROLL]*0.35f;
|
|
llAngles[ROLL] = viewAngles[ROLL]*0.45f;
|
|
|
|
//thoracic is added modified again by neckAngle calculations, so don't set it until then
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
}
|
|
|
|
void CG_G2ClientNeckAngles( centity_t *cent, const vec3_t lookAngles, vec3_t headAngles, vec3_t neckAngles, vec3_t thoracicAngles, vec3_t headClampMinAngles, vec3_t headClampMaxAngles )
|
|
{
|
|
vec3_t lA;
|
|
VectorCopy( lookAngles, lA );
|
|
//clamp the headangles (which should now be relative to the cervical (neck) angles
|
|
if ( lA[PITCH] < headClampMinAngles[PITCH] )
|
|
{
|
|
lA[PITCH] = headClampMinAngles[PITCH];
|
|
}
|
|
else if ( lA[PITCH] > headClampMaxAngles[PITCH] )
|
|
{
|
|
lA[PITCH] = headClampMaxAngles[PITCH];
|
|
}
|
|
|
|
if ( lA[YAW] < headClampMinAngles[YAW] )
|
|
{
|
|
lA[YAW] = headClampMinAngles[YAW];
|
|
}
|
|
else if ( lA[YAW] > headClampMaxAngles[YAW] )
|
|
{
|
|
lA[YAW] = headClampMaxAngles[YAW];
|
|
}
|
|
|
|
if ( lA[ROLL] < headClampMinAngles[ROLL] )
|
|
{
|
|
lA[ROLL] = headClampMinAngles[ROLL];
|
|
}
|
|
else if ( lA[ROLL] > headClampMaxAngles[ROLL] )
|
|
{
|
|
lA[ROLL] = headClampMaxAngles[ROLL];
|
|
}
|
|
|
|
//split it up between the neck and cranium
|
|
if ( thoracicAngles[PITCH] )
|
|
{//already been set above, blend them
|
|
thoracicAngles[PITCH] = (thoracicAngles[PITCH] + (lA[PITCH] * 0.4)) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
thoracicAngles[PITCH] = lA[PITCH] * 0.4;
|
|
}
|
|
if ( thoracicAngles[YAW] )
|
|
{//already been set above, blend them
|
|
thoracicAngles[YAW] = (thoracicAngles[YAW] + (lA[YAW] * 0.1)) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
thoracicAngles[YAW] = lA[YAW] * 0.1;
|
|
}
|
|
if ( thoracicAngles[ROLL] )
|
|
{//already been set above, blend them
|
|
thoracicAngles[ROLL] = (thoracicAngles[ROLL] + (lA[ROLL] * 0.1)) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
thoracicAngles[ROLL] = lA[ROLL] * 0.1;
|
|
}
|
|
|
|
neckAngles[PITCH] = lA[PITCH] * 0.2f;
|
|
neckAngles[YAW] = lA[YAW] * 0.3f;
|
|
neckAngles[ROLL] = lA[ROLL] * 0.3f;
|
|
|
|
headAngles[PITCH] = lA[PITCH] * 0.4;
|
|
headAngles[YAW] = lA[YAW] * 0.6;
|
|
headAngles[ROLL] = lA[ROLL] * 0.6;
|
|
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, headAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, neckAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
}
|
|
|
|
void CG_UpdateLookAngles( centity_t *cent, vec3_t lookAngles, float lookSpeed, float minPitch, float maxPitch, float minYaw, float maxYaw, float minRoll, float maxRoll )
|
|
{
|
|
if ( !cent || !cent->gent || !cent->gent->client )
|
|
{
|
|
return;
|
|
}
|
|
if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
|
|
{
|
|
//clamp so don't get "Exorcist" effect
|
|
if ( lookAngles[PITCH] > maxPitch )
|
|
{
|
|
lookAngles[PITCH] = maxPitch;
|
|
}
|
|
else if ( lookAngles[PITCH] < minPitch )
|
|
{
|
|
lookAngles[PITCH] = minPitch;
|
|
}
|
|
if ( lookAngles[YAW] > maxYaw )
|
|
{
|
|
lookAngles[YAW] = maxYaw;
|
|
}
|
|
else if ( lookAngles[YAW] < minYaw )
|
|
{
|
|
lookAngles[YAW] = minYaw;
|
|
}
|
|
if ( lookAngles[ROLL] > maxRoll )
|
|
{
|
|
lookAngles[ROLL] = maxRoll;
|
|
}
|
|
else if ( lookAngles[ROLL] < minRoll )
|
|
{
|
|
lookAngles[ROLL] = minRoll;
|
|
}
|
|
|
|
//slowly lerp to this new value
|
|
//Remember last headAngles
|
|
vec3_t oldLookAngles;
|
|
VectorCopy( cent->gent->client->renderInfo.lastHeadAngles, oldLookAngles );
|
|
vec3_t lookAnglesDiff;
|
|
VectorSubtract( lookAngles, oldLookAngles, lookAnglesDiff );
|
|
|
|
for ( int ang = 0; ang < 3; ang++ )
|
|
{
|
|
lookAnglesDiff[ang] = AngleNormalize180( lookAnglesDiff[ang] );
|
|
}
|
|
|
|
if( VectorLengthSquared( lookAnglesDiff ) )
|
|
{
|
|
lookAngles[PITCH] = AngleNormalize180( oldLookAngles[PITCH]+(lookAnglesDiff[PITCH]*cg.frameInterpolation*lookSpeed) );
|
|
lookAngles[YAW] = AngleNormalize180( oldLookAngles[YAW]+(lookAnglesDiff[YAW]*cg.frameInterpolation*lookSpeed) );
|
|
lookAngles[ROLL] = AngleNormalize180( oldLookAngles[ROLL]+(lookAnglesDiff[ROLL]*cg.frameInterpolation*lookSpeed) );
|
|
}
|
|
}
|
|
//Remember current lookAngles next time
|
|
VectorCopy( lookAngles, cent->gent->client->renderInfo.lastHeadAngles );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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
|
|
|
|
===============
|
|
*/
|
|
extern int PM_TurnAnimForLegsAnim( gentity_t *gent, int anim );
|
|
extern float PM_GetTimeScaleMod( gentity_t *gent );
|
|
void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t angles )
|
|
{
|
|
vec3_t headAngles, neckAngles, chestAngles, thoracicAngles = {0,0,0};//legsAngles, torsoAngles,
|
|
vec3_t ulAngles, llAngles;
|
|
//float speed;
|
|
//vec3_t velocity;
|
|
vec3_t lookAngles, viewAngles;
|
|
/*
|
|
float headYawClampMin, headYawClampMax;
|
|
float headPitchClampMin, headPitchClampMax;
|
|
float torsoYawSwingTolMin, torsoYawSwingTolMax;
|
|
float torsoYawClampMin, torsoYawClampMax;
|
|
float torsoPitchSwingTolMin, torsoPitchSwingTolMax;
|
|
float torsoPitchClampMin, torsoPitchClampMax;
|
|
float legsYawSwingTolMin, legsYawSwingTolMax;
|
|
float yawSpeed, maxYawSpeed, lookingSpeed;
|
|
*/
|
|
float lookAngleSpeed = LOOK_TALKING_SPEED;//shut up the compiler
|
|
//float swing, scale;
|
|
//int i;
|
|
qboolean looking = qfalse, talking = qfalse;
|
|
|
|
// Dead entity
|
|
if ( cent->gent && cent->gent->health <= 0 )
|
|
{
|
|
if ( cent->gent->hipsBone != -1 )
|
|
{
|
|
gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
|
|
}
|
|
|
|
VectorCopy( cent->lerpAngles, angles );
|
|
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
|
|
|
|
cent->pe.torso.pitchAngle = 0;
|
|
cent->pe.torso.yawAngle = 0;
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
|
|
|
|
cent->pe.legs.pitchAngle = angles[0];
|
|
cent->pe.legs.yawAngle = angles[1];
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = angles[1];
|
|
}
|
|
AnglesToAxis( angles, legs );
|
|
return;
|
|
}
|
|
|
|
if ( cent->gent && cent->gent->client
|
|
&& (cent->gent->client->NPC_class != CLASS_GONK )
|
|
&& (cent->gent->client->NPC_class != CLASS_INTERROGATOR)
|
|
&& (cent->gent->client->NPC_class != CLASS_SENTRY)
|
|
&& (cent->gent->client->NPC_class != CLASS_PROBE )
|
|
&& (cent->gent->client->NPC_class != CLASS_R2D2 )
|
|
&& (cent->gent->client->NPC_class != CLASS_R5D2)
|
|
&& (cent->gent->client->NPC_class != CLASS_ATST||!cent->gent->s.number) )
|
|
{// If we are rendering third person, we should just force the player body to always fully face
|
|
// whatever way they are looking, otherwise, you can end up with gun shots coming off of the
|
|
// gun at angles that just look really wrong.
|
|
|
|
//NOTENOTE: shots are coming out of the gun at ridiculous angles. The head & torso
|
|
//should pitch *some* when looking up and down...
|
|
VectorCopy( cent->lerpAngles, angles );
|
|
angles[PITCH] = 0;
|
|
|
|
if ( cent->gent->client )
|
|
{
|
|
if ( cent->gent->client->NPC_class != CLASS_ATST )
|
|
{
|
|
//FIXME: use actual swing/clamp tolerances?
|
|
if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE && !PM_InRoll( ¢->gent->client->ps ) )
|
|
{//on the ground
|
|
CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
|
|
}
|
|
else
|
|
{//face legs to front
|
|
CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
|
|
}
|
|
}
|
|
}
|
|
|
|
//VectorClear( viewAngles );
|
|
VectorCopy( cent->lerpAngles, viewAngles );
|
|
viewAngles[YAW] = viewAngles[ROLL] = 0;
|
|
viewAngles[PITCH] *= 0.5;
|
|
VectorCopy( viewAngles, lookAngles );
|
|
|
|
// if ( cent->gent && !Q_stricmp( "atst", cent->gent->NPC_type ) )
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
lookAngles[YAW] = 0;
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
VectorCopy( viewAngles, lookAngles );
|
|
}
|
|
else
|
|
{
|
|
if ( cg_turnAnims.integer && !in_camera && cent->gent->hipsBone >= 0 )
|
|
{
|
|
//override the hips bone with a turn anim when turning
|
|
//and clear it when we're not... does blend from and to parent actually work?
|
|
int startFrame, endFrame;
|
|
const qboolean animatingHips = gi.G2API_GetAnimRangeIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone, &startFrame, &endFrame );
|
|
|
|
//FIXME: make legs lag behind when turning in place, only play turn anim when legs have to catch up
|
|
if ( angles[YAW] == cent->pe.legs.yawAngle )
|
|
{
|
|
gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
|
|
}
|
|
else if ( VectorCompare( vec3_origin, cent->gent->client->ps.velocity ) )
|
|
{//FIXME: because of LegsYawFromMovement, we play the turnAnims when we stop running, which looks really bad.
|
|
int turnAnim = PM_TurnAnimForLegsAnim( cent->gent, cent->gent->client->ps.legsAnim );
|
|
if ( turnAnim != -1 && cent->gent->health > 0 )
|
|
{
|
|
animation_t *animations = level.knownAnimFileSets[cent->gent->client->clientInfo.animFileIndex].animations;
|
|
|
|
if ( !animatingHips || ( animations[turnAnim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again
|
|
{
|
|
float animSpeed = 50.0f / animations[turnAnim].frameLerp * PM_GetTimeScaleMod( cent->gent );
|
|
|
|
gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone,
|
|
animations[turnAnim].firstFrame, animations[turnAnim].firstFrame+animations[turnAnim].numFrames,
|
|
BONE_ANIM_OVERRIDE_LOOP/*|BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND*/, animSpeed, cg.time, -1, 100 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
|
|
}
|
|
}
|
|
|
|
CG_G2ClientSpineAngles( cent, viewAngles, angles, thoracicAngles, ulAngles, llAngles );
|
|
}
|
|
|
|
vec3_t trailingLegsAngles;
|
|
if ( cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
CG_ATSTLegsYaw( cent, trailingLegsAngles );
|
|
AnglesToAxis( trailingLegsAngles, legs );
|
|
angles[YAW] = trailingLegsAngles[YAW];
|
|
}
|
|
else
|
|
{
|
|
|
|
//set the legs.yawing field so we play the turning anim when turning in place
|
|
if ( angles[YAW] == cent->pe.legs.yawAngle )
|
|
{
|
|
cent->pe.legs.yawing = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cent->pe.legs.yawing = qtrue;
|
|
}
|
|
cent->pe.legs.yawAngle = angles[YAW];
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = angles[YAW];
|
|
}
|
|
if ( cent->gent->client->ps.eFlags & EF_FORCE_GRIPPED && cent->gent->client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{
|
|
vec3_t centFwd, centRt;
|
|
|
|
AngleVectors( cent->lerpAngles, centFwd, centRt, NULL );
|
|
angles[PITCH] = AngleNormalize180( DotProduct( cent->gent->client->ps.velocity, centFwd )/2 );
|
|
if ( angles[PITCH] > 90 )
|
|
{
|
|
angles[PITCH] = 90;
|
|
}
|
|
else if ( angles[PITCH] < -90 )
|
|
{
|
|
angles[PITCH] = -90;
|
|
}
|
|
angles[ROLL] = AngleNormalize180( DotProduct( cent->gent->client->ps.velocity, centRt )/10 );
|
|
if ( angles[ROLL] > 90 )
|
|
{
|
|
angles[ROLL] = 90;
|
|
}
|
|
else if ( angles[ROLL] < -90 )
|
|
{
|
|
angles[ROLL] = -90;
|
|
}
|
|
}
|
|
AnglesToAxis( angles, legs );
|
|
}
|
|
|
|
//clamp relative to forward of cervical bone!
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
looking = qfalse;
|
|
VectorCopy( vec3_origin, chestAngles );
|
|
}
|
|
else
|
|
{
|
|
//look at lookTarget!
|
|
float lookingSpeed = 0.3f;
|
|
looking = CG_CheckLookTarget( cent, lookAngles, &lookingSpeed );
|
|
//Now add head bob when talking
|
|
talking = CG_AddHeadBob( cent, lookAngles );
|
|
|
|
//NOTE: previously, lookAngleSpeed was always 0.25f for the player
|
|
//Figure out how fast head should be turning
|
|
if ( cent->pe.torso.yawing || cent->pe.torso.pitching )
|
|
{//If torso is turning, we want to turn head just as fast
|
|
if ( cent->gent->NPC )
|
|
{
|
|
lookAngleSpeed = cent->gent->NPC->stats.yawSpeed/150;//about 0.33 normally
|
|
}
|
|
else
|
|
{
|
|
lookAngleSpeed = cg_swingSpeed.value;
|
|
}
|
|
}
|
|
else if ( talking )
|
|
{//Slow for head bobbing
|
|
lookAngleSpeed = LOOK_TALKING_SPEED;
|
|
}
|
|
else if ( looking )
|
|
{//Not talking, set it up for looking at enemy, CheckLookTarget will scale it down if neccessary
|
|
lookAngleSpeed = lookingSpeed;
|
|
}
|
|
else if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
|
|
{//Not looking, not talking, head is returning from a talking head bob, use talking speed
|
|
lookAngleSpeed = LOOK_TALKING_SPEED;
|
|
}
|
|
|
|
if ( looking || talking )
|
|
{//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
|
|
//we should have a relative look angle, normalized to 180
|
|
cent->gent->client->renderInfo.lookingDebounceTime = cg.time + 1000;
|
|
}
|
|
else
|
|
{
|
|
//still have a relative look angle from above
|
|
}
|
|
|
|
CG_UpdateLookAngles( cent, lookAngles, lookAngleSpeed, -50.0f, 50.0f, -70.0f, 70.0f, -30.0f, 30.0f );
|
|
}
|
|
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
VectorCopy( cent->lerpAngles, lookAngles );
|
|
lookAngles[0] = lookAngles[2] = 0;
|
|
lookAngles[YAW] -= trailingLegsAngles[YAW];
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
}
|
|
else
|
|
{
|
|
vec3_t headClampMinAngles = {-25,-55,-10}, headClampMaxAngles = {50,50,10};
|
|
CG_G2ClientNeckAngles( cent, lookAngles, headAngles, neckAngles, thoracicAngles, headClampMinAngles, headClampMaxAngles );
|
|
}
|
|
return;
|
|
}
|
|
// All other entities
|
|
else if ( cent->gent && cent->gent->client )
|
|
{
|
|
if ( (cent->gent->client->NPC_class == CLASS_PROBE )
|
|
|| (cent->gent->client->NPC_class == CLASS_R2D2 )
|
|
|| (cent->gent->client->NPC_class == CLASS_R5D2)
|
|
|| (cent->gent->client->NPC_class == CLASS_ATST) )
|
|
{
|
|
VectorCopy( cent->lerpAngles, angles );
|
|
angles[PITCH] = 0;
|
|
|
|
//FIXME: use actual swing/clamp tolerances?
|
|
if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE )
|
|
{//on the ground
|
|
CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
|
|
}
|
|
else
|
|
{//face legs to front
|
|
CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
|
|
}
|
|
|
|
VectorCopy( cent->lerpAngles, viewAngles );
|
|
// viewAngles[YAW] = viewAngles[ROLL] = 0;
|
|
viewAngles[PITCH] *= 0.5;
|
|
VectorCopy( viewAngles, lookAngles );
|
|
|
|
lookAngles[1] = 0;
|
|
|
|
if ( cent->gent->client->NPC_class == CLASS_ATST )
|
|
{//body pitch
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT,POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
}
|
|
|
|
VectorCopy( viewAngles, lookAngles );
|
|
|
|
vec3_t trailingLegsAngles;
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
CG_ATSTLegsYaw( cent, trailingLegsAngles );
|
|
AnglesToAxis( trailingLegsAngles, legs );
|
|
}
|
|
else
|
|
{
|
|
//FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place
|
|
if ( angles[YAW] == cent->pe.legs.yawAngle )
|
|
{
|
|
cent->pe.legs.yawing = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cent->pe.legs.yawing = qtrue;
|
|
}
|
|
|
|
cent->pe.legs.yawAngle = angles[YAW];
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = angles[YAW];
|
|
}
|
|
AnglesToAxis( angles, legs );
|
|
}
|
|
|
|
// if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
|
|
// {
|
|
// looking = qfalse;
|
|
// }
|
|
// else
|
|
{ //look at lookTarget!
|
|
//FIXME: snaps to side when lets go of lookTarget... ?
|
|
float lookingSpeed = 0.3f;
|
|
looking = CG_CheckLookTarget( cent, lookAngles, &lookingSpeed );
|
|
lookAngles[PITCH] = lookAngles[ROLL] = 0;//droids can't pitch or roll their heads
|
|
if ( looking )
|
|
{//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
|
|
cent->gent->client->renderInfo.lookingDebounceTime = cg.time + 1000;
|
|
}
|
|
}
|
|
if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
|
|
{ //adjust for current body orientation
|
|
lookAngles[YAW] -= cent->pe.torso.yawAngle;
|
|
lookAngles[YAW] -= cent->pe.legs.yawAngle;
|
|
|
|
//normalize
|
|
lookAngles[YAW] = AngleNormalize180( lookAngles[YAW] );
|
|
|
|
//slowly lerp to this new value
|
|
//Remember last headAngles
|
|
vec3_t oldLookAngles;
|
|
VectorCopy( cent->gent->client->renderInfo.lastHeadAngles, oldLookAngles );
|
|
if( VectorCompare( oldLookAngles, lookAngles ) == qfalse )
|
|
{
|
|
//FIXME: This clamp goes off viewAngles,
|
|
//but really should go off the tag_torso's axis[0] angles, no?
|
|
lookAngles[YAW] = oldLookAngles[YAW]+(lookAngles[YAW]-oldLookAngles[YAW])*cg.frameInterpolation*0.25;
|
|
}
|
|
//Remember current lookAngles next time
|
|
VectorCopy( lookAngles, cent->gent->client->renderInfo.lastHeadAngles );
|
|
}
|
|
else
|
|
{//Remember current lookAngles next time
|
|
VectorCopy( lookAngles, cent->gent->client->renderInfo.lastHeadAngles );
|
|
}
|
|
if ( cent->gent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
VectorCopy( cent->lerpAngles, lookAngles );
|
|
lookAngles[0] = lookAngles[2] = 0;
|
|
lookAngles[YAW] -= trailingLegsAngles[YAW];
|
|
}
|
|
else
|
|
{
|
|
lookAngles[PITCH] = lookAngles[ROLL] = 0;
|
|
lookAngles[YAW] -= cent->pe.legs.yawAngle;
|
|
}
|
|
BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
//return;
|
|
}
|
|
else//if ( (cent->gent->client->NPC_class == CLASS_GONK ) || (cent->gent->client->NPC_class == CLASS_INTERROGATOR) || (cent->gent->client->NPC_class == CLASS_SENTRY) )
|
|
{
|
|
VectorCopy( cent->lerpAngles, angles );
|
|
cent->pe.torso.pitchAngle = 0;
|
|
cent->pe.torso.yawAngle = 0;
|
|
cent->pe.legs.pitchAngle = angles[0];
|
|
cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = angles[1];
|
|
AnglesToAxis( angles, legs );
|
|
//return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] )
|
|
{
|
|
vec3_t legsAngles, torsoAngles, headAngles;
|
|
vec3_t lookAngles, viewAngles;
|
|
float headYawClampMin, headYawClampMax;
|
|
float headPitchClampMin, headPitchClampMax;
|
|
float torsoYawSwingTolMin, torsoYawSwingTolMax;
|
|
float torsoYawClampMin, torsoYawClampMax;
|
|
float torsoPitchSwingTolMin, torsoPitchSwingTolMax;
|
|
float torsoPitchClampMin, torsoPitchClampMax;
|
|
float legsYawSwingTolMin, legsYawSwingTolMax;
|
|
float maxYawSpeed, yawSpeed, lookingSpeed;
|
|
float lookAngleSpeed = LOOK_TALKING_SPEED;//shut up the compiler
|
|
float swing, scale;
|
|
int i;
|
|
qboolean looking = qfalse, talking = qfalse;
|
|
|
|
if ( cg.renderingThirdPerson && cent->gent && cent->gent->s.number == 0 )
|
|
{
|
|
// If we are rendering third person, we should just force the player body to always fully face
|
|
// whatever way they are looking, otherwise, you can end up with gun shots coming off of the
|
|
// gun at angles that just look really wrong.
|
|
|
|
//NOTENOTE: shots are coming out of the gun at ridiculous angles. The head & torso
|
|
//should pitch *some* when looking up and down...
|
|
|
|
//VectorClear( viewAngles );
|
|
VectorCopy( cent->lerpAngles, viewAngles );
|
|
|
|
viewAngles[YAW] = viewAngles[ROLL] = 0;
|
|
viewAngles[PITCH] *= 0.5;
|
|
AnglesToAxis( viewAngles, head );
|
|
|
|
viewAngles[PITCH] *= 0.75;
|
|
cent->pe.torso.pitchAngle = viewAngles[PITCH];
|
|
cent->pe.torso.yawAngle = viewAngles[YAW];
|
|
AnglesToAxis( viewAngles, torso );
|
|
|
|
VectorCopy( cent->lerpAngles, lookAngles );
|
|
lookAngles[PITCH] = 0;
|
|
|
|
//FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place
|
|
if ( lookAngles[YAW] == cent->pe.legs.yawAngle )
|
|
{
|
|
cent->pe.legs.yawing = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cent->pe.legs.yawing = qtrue;
|
|
}
|
|
|
|
if ( cent->gent->client->ps.velocity[0] || cent->gent->client->ps.velocity[1] )
|
|
{
|
|
float moveYaw;
|
|
moveYaw = vectoyaw( cent->gent->client->ps.velocity );
|
|
lookAngles[YAW] = cent->lerpAngles[YAW] + AngleDelta( cent->lerpAngles[YAW], moveYaw );
|
|
}
|
|
|
|
cent->pe.legs.yawAngle = lookAngles[YAW];
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = lookAngles[YAW];
|
|
}
|
|
AnglesToAxis( lookAngles, legs );
|
|
|
|
return;
|
|
}
|
|
|
|
if(cent->currentState.eFlags & EF_NPC)
|
|
{
|
|
headYawClampMin = -cent->gent->client->renderInfo.headYawRangeLeft;
|
|
headYawClampMax = cent->gent->client->renderInfo.headYawRangeRight;
|
|
//These next two are only used for a calc below- this clamp is done in PM_UpdateViewAngles
|
|
headPitchClampMin = -cent->gent->client->renderInfo.headPitchRangeUp;
|
|
headPitchClampMax = cent->gent->client->renderInfo.headPitchRangeDown;
|
|
|
|
torsoYawSwingTolMin = headYawClampMin * 0.3;
|
|
torsoYawSwingTolMax = headYawClampMax * 0.3;
|
|
torsoPitchSwingTolMin = headPitchClampMin * 0.5;
|
|
torsoPitchSwingTolMax = headPitchClampMax * 0.5;
|
|
torsoYawClampMin = -cent->gent->client->renderInfo.torsoYawRangeLeft;
|
|
torsoYawClampMax = cent->gent->client->renderInfo.torsoYawRangeRight;
|
|
torsoPitchClampMin = -cent->gent->client->renderInfo.torsoPitchRangeUp;
|
|
torsoPitchClampMax = cent->gent->client->renderInfo.torsoPitchRangeDown;
|
|
|
|
legsYawSwingTolMin = torsoYawClampMin * 0.5;
|
|
legsYawSwingTolMax = torsoYawClampMax * 0.5;
|
|
|
|
if ( cent->gent && cent->gent->next_roff_time && cent->gent->next_roff_time >= cg.time )
|
|
{//Following a roff, body must keep up with head, yaw-wise
|
|
headYawClampMin =
|
|
headYawClampMax =
|
|
torsoYawSwingTolMin =
|
|
torsoYawSwingTolMax =
|
|
torsoYawClampMin =
|
|
torsoYawClampMax =
|
|
legsYawSwingTolMin =
|
|
legsYawSwingTolMax = 0;
|
|
}
|
|
|
|
yawSpeed = maxYawSpeed = cent->gent->NPC->stats.yawSpeed/150;//about 0.33 normally
|
|
}
|
|
else
|
|
{
|
|
headYawClampMin = -70;
|
|
headYawClampMax = 70;
|
|
|
|
//These next two are only used for a calc below- this clamp is done in PM_UpdateViewAngles
|
|
headPitchClampMin = -90;
|
|
headPitchClampMax = 90;
|
|
|
|
torsoYawSwingTolMin = -90;
|
|
torsoYawSwingTolMax = 90;
|
|
torsoPitchSwingTolMin = -90;
|
|
torsoPitchSwingTolMax = 90;
|
|
torsoYawClampMin = -90;
|
|
torsoYawClampMax = 90;
|
|
torsoPitchClampMin = -90;
|
|
torsoPitchClampMax = 90;
|
|
|
|
legsYawSwingTolMin = -90;
|
|
legsYawSwingTolMax = 90;
|
|
|
|
yawSpeed = maxYawSpeed = cg_swingSpeed.value;
|
|
}
|
|
|
|
if(yawSpeed <= 0)
|
|
{//Just in case
|
|
yawSpeed = 0.5f; //was 0.33
|
|
}
|
|
|
|
lookingSpeed = yawSpeed;
|
|
|
|
VectorCopy( cent->lerpAngles, headAngles );
|
|
headAngles[YAW] = AngleNormalize360( headAngles[YAW] );
|
|
VectorClear( legsAngles );
|
|
VectorClear( torsoAngles );
|
|
|
|
// --------- yaw -------------
|
|
|
|
//Clamp and swing the legs
|
|
legsAngles[YAW] = headAngles[YAW];
|
|
|
|
if(cent->gent->client->renderInfo.renderFlags & RF_LOCKEDANGLE)
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = cent->gent->client->renderInfo.lockYaw;
|
|
cent->pe.legs.yawing = qfalse;
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
}
|
|
else
|
|
{
|
|
qboolean alwaysFace = qfalse;
|
|
if ( cent->gent && cent->gent->health > 0 )
|
|
{
|
|
if ( cent->gent->enemy )
|
|
{
|
|
alwaysFace = qtrue;
|
|
}
|
|
if ( CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &legsAngles[YAW], headAngles[YAW], torsoYawClampMin, torsoYawClampMax, alwaysFace ) )
|
|
{
|
|
if ( legsAngles[YAW] == cent->pe.legs.yawAngle )
|
|
{
|
|
cent->pe.legs.yawing = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cent->pe.legs.yawing = qtrue;
|
|
}
|
|
cent->pe.legs.yawAngle = legsAngles[YAW];
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_SwingAngles( legsAngles[YAW], legsYawSwingTolMin, legsYawSwingTolMax, torsoYawClampMin, torsoYawClampMax, maxYawSpeed, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_SwingAngles( legsAngles[YAW], legsYawSwingTolMin, legsYawSwingTolMax, torsoYawClampMin, torsoYawClampMax, maxYawSpeed, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
if ( cent->gent->client )
|
|
{
|
|
cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
|
|
}
|
|
*/
|
|
|
|
// torso
|
|
// If applicable, swing the lower parts to catch up with the head
|
|
CG_SwingAngles( headAngles[YAW], torsoYawSwingTolMin, torsoYawSwingTolMax, headYawClampMin, headYawClampMax, yawSpeed, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing);
|
|
torsoAngles[YAW] = cent->pe.torso.yawAngle;
|
|
|
|
// ---------- pitch -----------
|
|
|
|
//As the body twists to its extents, the back tends to arch backwards
|
|
|
|
|
|
float dest;
|
|
// 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, torsoPitchSwingTolMin, torsoPitchSwingTolMax, torsoPitchClampMin, torsoPitchClampMax, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
|
|
torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
|
|
// --------- roll -------------
|
|
|
|
// pain twitch - FIXME: don't do this if you have no head (like droids?)
|
|
// Maybe need to have clamp angles for roll as well as pitch and yaw?
|
|
//CG_AddPainTwitch( cent, torsoAngles );
|
|
|
|
//----------- Special head looking ---------------
|
|
|
|
//FIXME: to clamp the head angles, figure out tag_head's offset from tag_torso and add
|
|
// that to whatever offset we're getting here... so turning the head in an
|
|
// anim that also turns the head doesn't allow the head to turn out of range.
|
|
|
|
//Start with straight ahead
|
|
VectorCopy( headAngles, viewAngles );
|
|
VectorCopy( headAngles, lookAngles );
|
|
|
|
//Remember last headAngles
|
|
VectorCopy( cent->gent->client->renderInfo.lastHeadAngles, headAngles );
|
|
|
|
//See if we're looking at someone/thing
|
|
looking = CG_CheckLookTarget( cent, lookAngles, &lookingSpeed );
|
|
|
|
//Now add head bob when talking
|
|
if ( cent->gent->client->clientInfo.extensions )
|
|
{
|
|
talking = CG_AddHeadBob( cent, lookAngles );
|
|
}
|
|
|
|
//Figure out how fast head should be turning
|
|
if ( cent->pe.torso.yawing || cent->pe.torso.pitching )
|
|
{//If torso is turning, we want to turn head just as fast
|
|
lookAngleSpeed = yawSpeed;
|
|
}
|
|
else if ( talking )
|
|
{//Slow for head bobbing
|
|
lookAngleSpeed = LOOK_TALKING_SPEED;
|
|
}
|
|
else if ( looking )
|
|
{//Not talking, set it up for looking at enemy, CheckLookTarget will scale it down if neccessary
|
|
lookAngleSpeed = lookingSpeed;
|
|
}
|
|
else if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
|
|
{//Not looking, not talking, head is returning from a talking head bob, use talking speed
|
|
lookAngleSpeed = LOOK_TALKING_SPEED;
|
|
}
|
|
|
|
if ( looking || talking )
|
|
{//Keep this type of looking for a second after stopped looking
|
|
cent->gent->client->renderInfo.lookingDebounceTime = cg.time + 1000;
|
|
}
|
|
|
|
if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
|
|
{
|
|
//Calc our actual desired head angles
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
lookAngles[i] = AngleNormalize360( cent->gent->client->renderInfo.headBobAngles[i] + lookAngles[i] );
|
|
}
|
|
|
|
if( VectorCompare( headAngles, lookAngles ) == qfalse )
|
|
{
|
|
//FIXME: This clamp goes off viewAngles,
|
|
//but really should go off the tag_torso's axis[0] angles, no?
|
|
CG_UpdateAngleClamp( lookAngles[PITCH], headPitchClampMin/1.25, headPitchClampMax/1.25, lookAngleSpeed, &headAngles[PITCH], viewAngles[PITCH] );
|
|
CG_UpdateAngleClamp( lookAngles[YAW], headYawClampMin/1.25, headYawClampMax/1.25, lookAngleSpeed, &headAngles[YAW], viewAngles[YAW] );
|
|
CG_UpdateAngleClamp( lookAngles[ROLL], -10, 10, lookAngleSpeed, &headAngles[ROLL], viewAngles[ROLL] );
|
|
}
|
|
|
|
if ( !cent->gent->enemy || cent->gent->enemy->s.number != cent->gent->client->renderInfo.lookTarget )
|
|
{
|
|
//NOTE: Hacky, yes, I know, but necc.
|
|
//We want to turn the body to follow the lookTarget
|
|
//ONLY IF WE DON'T HAVE AN ENEMY OR OUR ENEMY IS NOT OUR LOOKTARGET
|
|
//This is the piece of code that was making the enemies not face where
|
|
//they were actually aiming.
|
|
|
|
//Yaw change
|
|
swing = AngleSubtract( legsAngles[YAW], headAngles[YAW] );
|
|
scale = fabs( swing ) / ( torsoYawClampMax + 0.01 ); //NOTENOTE: Some ents have a clamp of 0, which is bad for division
|
|
|
|
scale *= LOOK_SWING_SCALE;
|
|
torsoAngles[YAW] = legsAngles[YAW] - ( swing * scale );
|
|
|
|
//Pitch change
|
|
swing = AngleSubtract( legsAngles[PITCH], headAngles[PITCH] );
|
|
scale = fabs( swing ) / ( torsoPitchClampMax + 0.01 ); //NOTENOTE: Some ents have a clamp of 0, which is bad for division
|
|
|
|
scale *= LOOK_SWING_SCALE;
|
|
torsoAngles[PITCH] = legsAngles[PITCH] - ( swing * scale );
|
|
}
|
|
}
|
|
else
|
|
{//Look straight ahead
|
|
VectorCopy( viewAngles, headAngles );
|
|
}
|
|
|
|
//Remember current headAngles next time
|
|
VectorCopy( headAngles, cent->gent->client->renderInfo.lastHeadAngles );
|
|
|
|
//-------------------------------------------------------------
|
|
|
|
// 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_TrailItem
|
|
===============
|
|
*/
|
|
void CG_TrailItem( centity_t *cent, qhandle_t hModel ) {
|
|
refEntity_t ent;
|
|
vec3_t angles;
|
|
vec3_t axis[3];
|
|
|
|
VectorCopy( cent->lerpAngles, angles );
|
|
angles[PITCH] = 0;
|
|
angles[ROLL] = 0;
|
|
AnglesToAxis( angles, axis );
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
VectorMA( cent->lerpOrigin, -24, axis[0], ent.origin );
|
|
ent.origin[2] += 20;
|
|
VectorScale( cg.autoAxis[0], 0.75, ent.axis[0] );
|
|
VectorScale( cg.autoAxis[1], 0.75, ent.axis[1] );
|
|
VectorScale( cg.autoAxis[2], 0.75, ent.axis[2] );
|
|
ent.hModel = hModel;
|
|
cgi_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerPowerups
|
|
===============
|
|
*/
|
|
extern void CG_Seeker( centity_t *cent );
|
|
void CG_PlayerPowerups( centity_t *cent )
|
|
{
|
|
if ( !cent->currentState.powerups ) {
|
|
return;
|
|
}
|
|
/*
|
|
|
|
// quad gives a dlight
|
|
if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) {
|
|
cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1 );
|
|
}
|
|
|
|
// redflag
|
|
if ( cent->currentState.powerups & ( 1 << PW_REDFLAG ) ) {
|
|
CG_TrailItem( cent, cgs.media.redFlagModel );
|
|
cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1, 0.2, 0.2 );
|
|
}
|
|
|
|
// blueflag
|
|
if ( cent->currentState.powerups & ( 1 << PW_BLUEFLAG ) ) {
|
|
CG_TrailItem( cent, cgs.media.blueFlagModel );
|
|
cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1 );
|
|
}
|
|
*/
|
|
// invul gives a dlight
|
|
// if ( cent->currentState.powerups & ( 1 << PW_BATTLESUIT ) )
|
|
// {
|
|
// cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.8f, 0.8f, 0.2f );
|
|
// }
|
|
|
|
// seeker coolness
|
|
/* if ( cent->currentState.powerups & ( 1 << PW_SEEKER ) )
|
|
{
|
|
// CG_Seeker(cent);
|
|
}*/
|
|
}
|
|
|
|
#define SHADOW_DISTANCE 128
|
|
static qboolean _PlayerShadow( const vec3_t origin, const float orientation, float *const shadowPlane, const float radius ) {
|
|
vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2};
|
|
trace_t trace;
|
|
float alpha;
|
|
|
|
// send a trace down from the player to the ground
|
|
VectorCopy( origin, end );
|
|
end[2] -= SHADOW_DISTANCE;
|
|
|
|
cgi_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID );
|
|
|
|
// no shadow if too high
|
|
if ( trace.fraction == 1.0 || (trace.startsolid && trace.allsolid) ) {
|
|
return qfalse;
|
|
}
|
|
|
|
*shadowPlane = trace.endpos[2] + 1;
|
|
|
|
if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows
|
|
return qtrue;
|
|
}
|
|
|
|
// 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
|
|
CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
|
|
orientation, 1,1,1,alpha, qfalse, radius, qtrue );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerShadow
|
|
|
|
Returns the Z component of the surface being shadowed
|
|
|
|
should it return a full plane instead of a Z?
|
|
===============
|
|
*/
|
|
static qboolean CG_PlayerShadow( centity_t *const cent, float *const shadowPlane ) {
|
|
*shadowPlane = 0;
|
|
|
|
if ( cg_shadows.integer == 0 ) {
|
|
return qfalse;
|
|
}
|
|
|
|
// no shadows when cloaked
|
|
if ( cent->currentState.powerups & ( 1 << PW_CLOAKED ))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (cent->gent->client->NPC_class == CLASS_ATST)
|
|
{
|
|
qboolean bShadowed;
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tempAngles,sideOrigin;
|
|
tempAngles[PITCH] = 0;
|
|
tempAngles[YAW] = cent->pe.legs.yawAngle;
|
|
tempAngles[ROLL] = 0;
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footLBolt,
|
|
&boltMatrix, tempAngles, cent->lerpOrigin,
|
|
cg.time, cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
|
|
sideOrigin[2] += 30; //fudge up a bit for coplaner
|
|
bShadowed = _PlayerShadow(sideOrigin, 0, shadowPlane, 28);
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footRBolt,
|
|
&boltMatrix, tempAngles, cent->lerpOrigin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
|
|
sideOrigin[2] += 30; //fudge up a bit for coplaner
|
|
bShadowed = (qboolean)(_PlayerShadow(sideOrigin, 0, shadowPlane, 28) || bShadowed);
|
|
|
|
bShadowed = (qboolean)(_PlayerShadow(cent->lerpOrigin, cent->pe.legs.yawAngle, shadowPlane, 64) || bShadowed);
|
|
return bShadowed;
|
|
}
|
|
else
|
|
{
|
|
return _PlayerShadow(cent->lerpOrigin, cent->pe.legs.yawAngle, shadowPlane, 16);
|
|
}
|
|
|
|
}
|
|
|
|
void _PlayerSplash( const vec3_t origin, const vec3_t velocity, const float radius, const int maxUp )
|
|
{
|
|
static vec3_t WHITE={1,1,1};
|
|
vec3_t start, end;
|
|
trace_t trace;
|
|
int contents;
|
|
|
|
VectorCopy( origin, 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 = cgi_CM_PointContents( end, 0 );
|
|
if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy( origin, start );
|
|
if ( maxUp < 32 )
|
|
{//our head may actually be lower than 32 above our origin
|
|
start[2] += maxUp;
|
|
}
|
|
else
|
|
{
|
|
start[2] += 32;
|
|
}
|
|
|
|
// if the head isn't out of liquid, don't make a mark
|
|
contents = cgi_CM_PointContents( start, 0 );
|
|
if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// trace down to find the surface
|
|
cgi_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
|
|
|
|
if ( trace.fraction == 1.0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy( trace.endpos, end );
|
|
|
|
end[0] += Q_flrand(-1.0f, 1.0f) * 3.0f;
|
|
end[1] += Q_flrand(-1.0f, 1.0f) * 3.0f;
|
|
end[2] += 1.0f; //fudge up
|
|
|
|
int t = VectorLengthSquared( velocity );
|
|
|
|
if ( t > 8192 ) // oh, magic number
|
|
{
|
|
t = 8192;
|
|
}
|
|
|
|
float alpha = ( t / 8192.0f ) * 0.6f + 0.2f;
|
|
|
|
FX_AddOrientedParticle( end, trace.plane.normal, NULL, NULL,
|
|
6.0f, radius + Q_flrand(0.0f, 1.0f) * 48.0f, 0,
|
|
alpha, 0.0f, 0.0f,
|
|
WHITE, WHITE, 0.0f,
|
|
Q_flrand(0.0f, 1.0f) * 360, Q_flrand(-1.0f, 1.0f) * 6.0f, NULL, NULL, 0.0f, 0 ,0, 1200,
|
|
cgs.media.wakeMarkShader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerSplash
|
|
|
|
Draw a mark at the water surface
|
|
===============
|
|
*/
|
|
void CG_PlayerSplash( centity_t *cent )
|
|
{
|
|
if ( !cg_shadows.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cent->gent && cent->gent->client )
|
|
{
|
|
gclient_t *cl = cent->gent->client;
|
|
|
|
if ( cent->gent->disconnectDebounceTime < cg.time ) // can't do these expanding ripples all the time
|
|
{
|
|
if ( cl->NPC_class == CLASS_ATST )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tempAngles, sideOrigin;
|
|
|
|
tempAngles[PITCH] = 0;
|
|
tempAngles[YAW] = cent->pe.legs.yawAngle;
|
|
tempAngles[ROLL] = 0;
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footLBolt,
|
|
&boltMatrix, tempAngles, cent->lerpOrigin,
|
|
cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
|
|
sideOrigin[2] += 22; //fudge up a bit for coplaner
|
|
_PlayerSplash( sideOrigin, cl->ps.velocity, 42, cent->gent->maxs[2] );
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footRBolt,
|
|
&boltMatrix, tempAngles, cent->lerpOrigin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
|
|
sideOrigin[2] += 22; //fudge up a bit for coplaner
|
|
|
|
_PlayerSplash( sideOrigin, cl->ps.velocity, 42, cent->gent->maxs[2] );
|
|
}
|
|
else
|
|
{
|
|
// player splash mark
|
|
_PlayerSplash( cent->lerpOrigin, cl->ps.velocity, 36, cl->renderInfo.eyePoint[2] - cent->lerpOrigin[2] + 5 );
|
|
}
|
|
|
|
cent->gent->disconnectDebounceTime = cg.time + 125 + Q_flrand(0.0f, 1.0f) * 50.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_LightningBolt
|
|
===============
|
|
*/
|
|
|
|
void CG_LightningBolt( centity_t *cent, vec3_t origin )
|
|
{
|
|
// FIXME: This sound also plays when the weapon first fires which causes little sputtering sounds..not exactly cool
|
|
// Must be currently firing
|
|
if ( !( cent->currentState.eFlags & EF_FIRING ) )
|
|
return;
|
|
|
|
//Must be a durational weapon
|
|
// if ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.eFlags & EF_ALT_FIRING )
|
|
// { /*nothing*/ }
|
|
// else
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* trace_t trace;
|
|
gentity_t *traceEnt;
|
|
vec3_t end, forward, org, angs;
|
|
qboolean spark = qfalse, impact = qtrue;//, weak = qfalse;
|
|
|
|
// for lightning weapons coming from the player, it had better hit the crosshairs or else..
|
|
if ( cent->gent->s.number )
|
|
{
|
|
VectorCopy( origin, org );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( cg.refdef.vieworg, org );
|
|
}
|
|
|
|
// Find the impact point of the beam
|
|
VectorCopy( cent->lerpAngles, angs );
|
|
|
|
AngleVectors( angs, forward, NULL, NULL );
|
|
|
|
VectorMA( org, weaponData[cent->currentState.weapon].range, forward, end );
|
|
|
|
CG_Trace( &trace, org, vec3_origin, vec3_origin, end, cent->currentState.number, MASK_SHOT );
|
|
traceEnt = &g_entities[ trace.entityNum ];
|
|
|
|
// Make sparking be a bit less frame-rate dependent..also never add sparking when we hit a surface with a NOIMPACT flag
|
|
if ( cent->gent->fx_time < cg.time && !(trace.surfaceFlags & SURF_NOIMPACT ))
|
|
{
|
|
spark = qtrue;
|
|
cent->gent->fx_time = cg.time + Q_flrand(0.0f, 1.0f) * 100 + 100;
|
|
}
|
|
|
|
// Don't draw certain kinds of impacts when it hits a player and such..or when we hit a surface with a NOIMPACT flag
|
|
if ( (traceEnt->takedamage && traceEnt->client) || (trace.surfaceFlags & SURF_NOIMPACT) )
|
|
{
|
|
impact = qfalse;
|
|
}
|
|
|
|
// Add in the effect
|
|
switch ( cent->currentState.weapon )
|
|
{
|
|
case WP_DEMP2:
|
|
// vec3_t org;
|
|
|
|
extern void FX_DEMP2_AltBeam( vec3_t start, vec3_t end, vec3_t normal, //qboolean spark,
|
|
vec3_t targ1, vec3_t targ2 );
|
|
|
|
// Move the beam back a bit to help cover up the poly edges on the fire beam
|
|
// VectorMA( origin, -4, forward, org );
|
|
// FIXME: Looks and works like ASS, so don't let people see it until it improves
|
|
FX_DEMP2_AltBeam( origin, trace.endpos, trace.plane.normal, cent->gent->pos1, cent->gent->pos2 );
|
|
break;
|
|
|
|
}
|
|
*/
|
|
}
|
|
|
|
//-------------------------------------------
|
|
void CG_ForcePushBlur( const vec3_t org )
|
|
{
|
|
localEntity_t *ex;
|
|
|
|
ex = CG_AllocLocalEntity();
|
|
ex->leType = LE_PUFF;
|
|
ex->refEntity.reType = RT_SPRITE;
|
|
ex->radius = 2.0f;
|
|
ex->startTime = cg.time;
|
|
ex->endTime = ex->startTime + 120;
|
|
VectorCopy( org, ex->pos.trBase );
|
|
ex->pos.trTime = cg.time;
|
|
ex->pos.trType = TR_LINEAR;
|
|
VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta );
|
|
|
|
ex->color[0] = 24;
|
|
ex->color[1] = 32;
|
|
ex->color[2] = 40;
|
|
ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/effects/forcePush" );
|
|
|
|
ex = CG_AllocLocalEntity();
|
|
ex->leType = LE_PUFF;
|
|
ex->refEntity.reType = RT_SPRITE;
|
|
ex->refEntity.rotation = 180.0f;
|
|
ex->radius = 2.0f;
|
|
ex->startTime = cg.time;
|
|
ex->endTime = ex->startTime + 120;
|
|
VectorCopy( org, ex->pos.trBase );
|
|
ex->pos.trTime = cg.time;
|
|
ex->pos.trType = TR_LINEAR;
|
|
VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta );
|
|
|
|
ex->color[0] = 24;
|
|
ex->color[1] = 32;
|
|
ex->color[2] = 40;
|
|
ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/effects/forcePush" );
|
|
}
|
|
|
|
static void CG_ForcePushBodyBlur( centity_t *cent, const vec3_t origin, vec3_t tempAngles )
|
|
{
|
|
vec3_t fxOrg;
|
|
mdxaBone_t boltMatrix;
|
|
|
|
// Head blur
|
|
CG_ForcePushBlur( cent->gent->client->renderInfo.eyePoint );
|
|
|
|
// Do a torso based blur
|
|
if (cent->gent->torsoBolt>=0)
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->torsoBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
|
|
if (cent->gent->handRBolt>=0)
|
|
{
|
|
// Do a right-hand based blur
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handRBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
|
|
if (cent->gent->handLBolt>=0)
|
|
{
|
|
// Do a left-hand based blur
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handLBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
|
|
// Do the knees
|
|
if (cent->gent->kneeLBolt>=0)
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->kneeLBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
|
|
if (cent->gent->kneeRBolt>=0)
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->kneeRBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
|
|
if (cent->gent->elbowLBolt>=0)
|
|
{
|
|
// Do the elbows
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->elbowLBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
if (cent->gent->elbowRBolt>=0)
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->elbowRBolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
CG_ForcePushBlur( fxOrg );
|
|
}
|
|
}
|
|
|
|
static void CG_ForceElectrocution( centity_t *cent, const vec3_t origin, vec3_t tempAngles )
|
|
{
|
|
// Undoing for now, at least this code should compile if I ( or anyone else ) decides to work on this effect
|
|
qboolean found = qfalse;
|
|
vec3_t fxOrg, fxOrg2, dir;
|
|
vec3_t rgb = {1.0f,1.0f,1.0f};
|
|
mdxaBone_t boltMatrix;
|
|
|
|
int bolt=-1;
|
|
int iter=0;
|
|
// Pick a random start point
|
|
while (bolt<0)
|
|
{
|
|
int test;
|
|
if (iter>5)
|
|
{
|
|
test=iter-5;
|
|
}
|
|
else
|
|
{
|
|
test=Q_irand(0,6);
|
|
}
|
|
switch(test)
|
|
{
|
|
case 0:
|
|
// Right Elbow
|
|
bolt=cent->gent->elbowRBolt;
|
|
break;
|
|
case 1:
|
|
// Left Hand
|
|
bolt=cent->gent->handLBolt;
|
|
break;
|
|
case 2:
|
|
// Right hand
|
|
bolt=cent->gent->handRBolt;
|
|
break;
|
|
case 3:
|
|
// Left Foot
|
|
bolt=cent->gent->footLBolt;
|
|
break;
|
|
case 4:
|
|
// Right foot
|
|
bolt=cent->gent->footRBolt;
|
|
break;
|
|
case 5:
|
|
// Torso
|
|
bolt=cent->gent->torsoBolt;
|
|
break;
|
|
case 6:
|
|
default:
|
|
// Left Elbow
|
|
bolt=cent->gent->elbowLBolt;
|
|
break;
|
|
}
|
|
if (++iter==20)
|
|
break;
|
|
}
|
|
if (bolt>=0)
|
|
{
|
|
found = gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt,
|
|
&boltMatrix, tempAngles, origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale);
|
|
}
|
|
// Make sure that it's safe to even try and get these values out of the Matrix, otherwise the values could be garbage
|
|
if ( found )
|
|
{
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
if ( Q_flrand(0.0f, 1.0f) > 0.5f )
|
|
{
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, dir );
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
|
|
}
|
|
|
|
// Add some fudge, makes us not normalized, but that isn't really important
|
|
dir[0] += Q_flrand(-1.0f, 1.0f) * 0.4f;
|
|
dir[1] += Q_flrand(-1.0f, 1.0f) * 0.4f;
|
|
dir[2] += Q_flrand(-1.0f, 1.0f) * 0.4f;
|
|
}
|
|
else
|
|
{
|
|
// Just use the lerp Origin and a random direction
|
|
VectorCopy( cent->lerpOrigin, fxOrg );
|
|
VectorSet( dir, Q_flrand(-1.0f, 1.0f), Q_flrand(-1.0f, 1.0f), Q_flrand(-1.0f, 1.0f) ); // Not normalized, but who cares.
|
|
if ( cent->gent && cent->gent->client )
|
|
{
|
|
switch ( cent->gent->client->NPC_class )
|
|
{
|
|
case CLASS_PROBE:
|
|
fxOrg[2] += 50;
|
|
break;
|
|
case CLASS_MARK1:
|
|
fxOrg[2] += 50;
|
|
break;
|
|
case CLASS_ATST:
|
|
fxOrg[2] += 120;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorMA( fxOrg, Q_flrand(0.0f, 1.0f) * 40 + 40, dir, fxOrg2 );
|
|
|
|
trace_t tr;
|
|
|
|
CG_Trace( &tr, fxOrg, NULL, NULL, fxOrg2, -1, CONTENTS_SOLID );
|
|
|
|
if ( tr.fraction < 1.0f || Q_flrand(0.0f, 1.0f) > 0.94f )
|
|
{
|
|
FX_AddElectricity( fxOrg, tr.endpos,
|
|
1.5f, 4.0f, 0.0f,
|
|
1.0f, 0.5f, 0.0f,
|
|
rgb, rgb, 0.0f,
|
|
5.5f, Q_flrand(0.0f, 1.0f) * 50 + 100, cgs.media.boltShader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR | FX_BRANCH | FX_GROW | FX_TAPER );
|
|
}
|
|
}
|
|
/*
|
|
===============
|
|
CG_AddRefEntityWithPowerups
|
|
|
|
Adds a piece with modifications or duplications for powerups
|
|
===============
|
|
*/
|
|
void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, centity_t *cent )
|
|
{
|
|
refEntity_t hiltEnt;
|
|
if ( !cent )
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
return;
|
|
}
|
|
gentity_t *gent = cent->gent;
|
|
if ( !gent )
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
return;
|
|
}
|
|
if ( gent->client->ps.powerups[PW_DISRUPTION] < cg.time )
|
|
{//disruptor
|
|
if (( powerups & ( 1 << PW_DISRUPTION )))
|
|
{
|
|
//stop drawing him after this effect
|
|
gent->client->ps.eFlags |= EF_NODRAW;
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool player1stPersonSaber = CG_getPlayer1stPersonSaber(cent);
|
|
|
|
// if ( gent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 )
|
|
// {
|
|
// centity_t *cent = &cg_entities[gent->s.number];
|
|
// cgi_S_AddLoopingSound( 0, cent->lerpOrigin, vec3_origin, cgs.media.overchargeLoopSound );
|
|
// }
|
|
|
|
if (player1stPersonSaber) {
|
|
ent->renderfx = RF_THIRD_PERSON;
|
|
}
|
|
|
|
// If certain states are active, we don't want to add in the regular body
|
|
if ( !gent->client->ps.powerups[PW_CLOAKED] &&
|
|
!gent->client->ps.powerups[PW_UNCLOAKING] &&
|
|
!gent->client->ps.powerups[PW_DISRUPTION] )
|
|
{
|
|
cgi_R_AddRefEntityToScene(ent);
|
|
}
|
|
|
|
if (player1stPersonSaber && !cent->currentState.saberInFlight && !vr->item_selector)
|
|
{
|
|
memset( &hiltEnt, 0, sizeof(refEntity_t) );
|
|
hiltEnt.renderfx = RF_DEPTHHACK;
|
|
hiltEnt.hModel = cgi_R_RegisterModel( "models/weapons2/saber/saber_w.md3" );
|
|
vec3_t angles;
|
|
BG_CalculateVRSaberPosition(hiltEnt.origin, hiltEnt.angles);
|
|
VectorCopy(hiltEnt.origin, hiltEnt.oldorigin);
|
|
vec3_t axis[3];
|
|
AnglesToAxis(hiltEnt.angles, axis);
|
|
VectorSubtract(vec3_origin, axis[2], hiltEnt.axis[0]);
|
|
VectorCopy(axis[1], hiltEnt.axis[1]);
|
|
VectorCopy(axis[0], hiltEnt.axis[2]);
|
|
for (int i = 0; i < 3; ++i)
|
|
VectorScale(hiltEnt.axis[i], 0.85f, hiltEnt.axis[i]);
|
|
|
|
cgi_R_AddRefEntityToScene(&hiltEnt);
|
|
|
|
static int playingSaberSwingSound = 0;
|
|
if (vr->primaryVelocityTriggeredAttack && ((cg.time - playingSaberSwingSound) > 800))
|
|
{
|
|
cgi_S_StartSound ( hiltEnt.origin, cent->gent->s.number, CHAN_AUTO, cgi_S_RegisterSound( va( "sound/weapons/saber/saberhup%d.wav", Q_irand( 1, 9 ) ) ) );
|
|
playingSaberSwingSound = cg.time;
|
|
}
|
|
}
|
|
|
|
// Disruptor Gun Alt-fire
|
|
if ( gent->client->ps.powerups[PW_DISRUPTION] )
|
|
{
|
|
// I guess when something dies, it looks like pos1 gets set to the impact point on death, we can do fun stuff with this
|
|
vec3_t tempAng;
|
|
VectorSubtract( gent->pos1, ent->origin, ent->oldorigin );
|
|
//er, adjust this to get the proper position in model space... account for yaw
|
|
float tempLength = VectorNormalize( ent->oldorigin );
|
|
vectoangles( ent->oldorigin, tempAng );
|
|
tempAng[YAW] -= gent->client->ps.viewangles[YAW];
|
|
AngleVectors( tempAng, ent->oldorigin, NULL, NULL );
|
|
VectorScale( ent->oldorigin, tempLength, ent->oldorigin );
|
|
|
|
ent->endTime = gent->fx_time;
|
|
ent->renderfx |= (RF_DISINTEGRATE2);
|
|
|
|
ent->customShader = cgi_R_RegisterShader( "gfx/effects/burn" );
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
|
|
ent->renderfx &= ~(RF_DISINTEGRATE2);
|
|
ent->renderfx |= (RF_DISINTEGRATE1);
|
|
ent->customShader = 0;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
|
|
if ( cg.time - ent->endTime < 1000 && (cg_timescale.value * cg_timescale.value * Q_flrand(0.0f, 1.0f)) > 0.05f )
|
|
{
|
|
vec3_t fxOrg;
|
|
mdxaBone_t boltMatrix;
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, gent->playerModel, gent->torsoBolt,
|
|
&boltMatrix, gent->currentAngles, ent->origin, cg.time,
|
|
cgs.model_draw, gent->s.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
|
|
|
|
VectorMA( fxOrg, -18, cg.refdef.viewaxis[0], fxOrg );
|
|
fxOrg[2] += Q_flrand(-1.0f, 1.0f) * 20;
|
|
theFxScheduler.PlayEffect( "disruptor/death_smoke", fxOrg );
|
|
|
|
if ( Q_flrand(0.0f, 1.0f) > 0.5f )
|
|
{
|
|
theFxScheduler.PlayEffect( "disruptor/death_smoke", fxOrg );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cloaking & Uncloaking Technology
|
|
//----------------------------------------
|
|
|
|
if (( powerups & ( 1 << PW_UNCLOAKING )))
|
|
{//in the middle of cloaking
|
|
float perc = (float)(gent->client->ps.powerups[PW_UNCLOAKING] - cg.time) / 2000.0f;
|
|
if (( powerups & ( 1 << PW_CLOAKED )))
|
|
{//actually cloaking, so reverse it
|
|
perc = 1.0f - perc;
|
|
}
|
|
|
|
if ( perc >= 0.0f && perc <= 1.0f )
|
|
{
|
|
ent->renderfx &= ~RF_ALPHA_FADE;
|
|
ent->renderfx |= RF_RGB_TINT;
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255.0f * perc;
|
|
ent->shaderRGBA[3] = 0;
|
|
ent->customShader = cgs.media.cloakedShader;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255;
|
|
ent->shaderRGBA[3] = 255 * (1.0f - perc); // let model alpha in
|
|
ent->customShader = 0; // use regular skin
|
|
ent->renderfx &= ~RF_RGB_TINT;
|
|
ent->renderfx |= RF_ALPHA_FADE;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
}
|
|
else if (( powerups & ( 1 << PW_CLOAKED )))
|
|
{//fully cloaked
|
|
ent->renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE);
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = ent->shaderRGBA[3] = 255;
|
|
ent->customShader = cgs.media.cloakedShader;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
// Electricity
|
|
//------------------------------------------------
|
|
if ( (powerups & ( 1 << PW_SHOCKED )) )
|
|
{
|
|
int dif = gent->client->ps.powerups[PW_SHOCKED] - cg.time;
|
|
|
|
if ( dif > 0 && Q_flrand(0.0f, 1.0f) > 0.4f )
|
|
{
|
|
// fade out over the last 500 ms
|
|
int brightness = 255;
|
|
|
|
if ( dif < 500 )
|
|
{
|
|
brightness = floor((dif - 500.0f) / 500.0f * 255.0f );
|
|
}
|
|
|
|
ent->renderfx |= RF_RGB_TINT;
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = brightness;
|
|
ent->shaderRGBA[3] = 255;
|
|
|
|
if ( rand() & 1 )
|
|
{
|
|
ent->customShader = cgs.media.electricBodyShader;
|
|
}
|
|
else
|
|
{
|
|
ent->customShader = cgs.media.electricBody2Shader;
|
|
}
|
|
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
|
|
if ( Q_flrand(0.0f, 1.0f) > 0.9f )
|
|
cgi_S_StartSound ( ent->origin, gent->s.number, CHAN_AUTO, cgi_S_RegisterSound( "sound/effects/energy_crackle.wav" ) );
|
|
}
|
|
}
|
|
|
|
// FORCE speed does blur trails
|
|
//------------------------------------------------------
|
|
if ( gent->client->ps.forcePowersActive & (1 << FP_SPEED)
|
|
&& (gent->s.number || cg.renderingThirdPerson) ) // looks dumb doing this with first peron mode on
|
|
{
|
|
localEntity_t *ex;
|
|
|
|
ex = CG_AllocLocalEntity();
|
|
ex->leType = LE_FADE_MODEL;
|
|
memcpy( &ex->refEntity, ent, sizeof( refEntity_t ));
|
|
|
|
ex->refEntity.renderfx |= RF_ALPHA_FADE;
|
|
ex->startTime = cg.time;
|
|
ex->endTime = ex->startTime + 75;
|
|
VectorCopy( ex->refEntity.origin, ex->pos.trBase );
|
|
VectorClear( ex->pos.trDelta );
|
|
|
|
ex->color[0] = ex->color[1] = ex->color[2] = 255.0f;
|
|
ex->color[3] = 50.0f;
|
|
}
|
|
|
|
// Personal Shields
|
|
//------------------------
|
|
if ( powerups & ( 1 << PW_BATTLESUIT ))
|
|
{
|
|
float diff = gent->client->ps.powerups[PW_BATTLESUIT] - cg.time;
|
|
float t;
|
|
|
|
if ( diff > 0 )
|
|
{
|
|
t = 1.0f - ( diff / (ARMOR_EFFECT_TIME * 2.0f));
|
|
// Only display when we have damage
|
|
if ( t < 0.0f || t > 1.0f )
|
|
{
|
|
}
|
|
else
|
|
{
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255.0f * t;
|
|
ent->shaderRGBA[3] = 255;
|
|
ent->renderfx &= ~RF_ALPHA_FADE;
|
|
ent->renderfx |= RF_RGB_TINT;
|
|
ent->customShader = cgs.media.personalShieldShader;
|
|
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Galak Mech shield bubble
|
|
//------------------------------------------------------
|
|
if ( powerups & ( 1 << PW_GALAK_SHIELD ))
|
|
{
|
|
refEntity_t tent;
|
|
|
|
memset( &tent, 0, sizeof( refEntity_t ));
|
|
|
|
tent.reType = RT_LATHE;
|
|
|
|
// Setting up the 2d control points, these get swept around to make a 3D lathed model
|
|
VectorSet2( tent.axis[0], 0.5, 0 ); // start point of curve
|
|
VectorSet2( tent.axis[1], 50, 85 ); // control point 1
|
|
VectorSet2( tent.axis[2], 135, -100 ); // control point 2
|
|
VectorSet2( tent.oldorigin, 0, -90 ); // end point of curve
|
|
|
|
if ( gent->client->poisonTime && gent->client->poisonTime + 1000 > cg.time )
|
|
{
|
|
VectorCopy( gent->pos4, tent.lightingOrigin );
|
|
tent.frame = gent->client->poisonTime;
|
|
}
|
|
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t angles = {0,gent->client->ps.legsYaw,0};
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, gent->playerModel, gent->genericBolt1, &boltMatrix, angles, cent->lerpOrigin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tent.origin );// pass in the emitter origin here
|
|
|
|
tent.endTime = gent->fx_time + 1000; // if you want the shell to build around the guy, pass in a time that is 1000ms after the start of the turn-on-effect
|
|
tent.customShader = cgi_R_RegisterShader( "gfx/effects/irid_shield" );
|
|
|
|
cgi_R_AddRefEntityToScene( &tent );
|
|
}
|
|
|
|
// Invincibility -- effect needs work
|
|
//------------------------------------------------------
|
|
if ( powerups & ( 1 << PW_INVINCIBLE ))
|
|
{
|
|
theFxScheduler.PlayEffect( cgs.effects.forceInvincibility, cent->lerpOrigin );
|
|
}
|
|
|
|
// Healing -- could use some work....maybe also make it NOT be framerate dependant
|
|
//------------------------------------------------------
|
|
/* if ( powerups & ( 1 << PW_HEALING ))
|
|
{
|
|
vec3_t axis[3];
|
|
|
|
AngleVectors( cent->gent->client->renderInfo.eyeAngles, axis[0], axis[1], axis[2] );
|
|
|
|
theFxScheduler.PlayEffect( cgs.effects.forceHeal, cent->gent->client->renderInfo.eyePoint, axis );
|
|
}
|
|
*/
|
|
// Push Blur
|
|
if ( gent->forcePushTime > cg.time && gi.G2API_HaveWeGhoul2Models( cent->gent->ghoul2 ) )
|
|
{
|
|
CG_ForcePushBlur( ent->origin );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
-------------------------
|
|
CG_G2SetHeadBlink
|
|
-------------------------
|
|
*/
|
|
static void CG_G2SetHeadBlink( centity_t *cent, qboolean bStart )
|
|
{
|
|
if ( !cent )
|
|
{
|
|
return;
|
|
}
|
|
gentity_t *gent = cent->gent;
|
|
//FIXME: get these boneIndices game-side and pass it down?
|
|
//FIXME: need a version of this that *doesn't* need the mFileName in the ghoul2
|
|
const int hLeye = gi.G2API_GetBoneIndex( &gent->ghoul2[0], "leye", qtrue );
|
|
if (hLeye == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
vec3_t desiredAngles = {0};
|
|
int blendTime = 80;
|
|
qboolean bWink = qfalse;
|
|
|
|
if (bStart)
|
|
{
|
|
desiredAngles[YAW] = -50;
|
|
if ( !in_camera && Q_flrand(0.0f, 1.0f) > 0.95f )
|
|
{
|
|
bWink = qtrue;
|
|
blendTime /=3;
|
|
}
|
|
}
|
|
gi.G2API_SetBoneAnglesIndex( &gent->ghoul2[gent->playerModel], hLeye, desiredAngles,
|
|
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time );
|
|
const int hReye = gi.G2API_GetBoneIndex( &gent->ghoul2[0], "reye", qtrue );
|
|
if (hReye == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!bWink)
|
|
gi.G2API_SetBoneAnglesIndex( &gent->ghoul2[gent->playerModel], hReye, desiredAngles,
|
|
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
CG_G2SetHeadAnims
|
|
-------------------------
|
|
*/
|
|
static void CG_G2SetHeadAnim( centity_t *cent, int anim )
|
|
{
|
|
gentity_t *gent = cent->gent;
|
|
const int blendTime = 50;
|
|
const animation_t *animations = level.knownAnimFileSets[gent->client->clientInfo.animFileIndex].animations;
|
|
int animFlags = BONE_ANIM_OVERRIDE ;//| BONE_ANIM_BLEND;
|
|
// animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps).
|
|
// float timeScaleMod = (cg_timescale.value&&gent&&gent->s.clientNum==0&&!player_locked&&!MatrixMode&&gent->client->ps.forcePowersActive&(1<<FP_SPEED))?(1.0/cg_timescale.value):1.0;
|
|
const float timeScaleMod = (cg_timescale.value)?(1.0/cg_timescale.value):1.0;
|
|
float animSpeed = 50.0f / animations[anim].frameLerp * timeScaleMod;
|
|
|
|
if (animations[anim].numFrames <= 0)
|
|
{
|
|
return;
|
|
}
|
|
if (anim == FACE_DEAD)
|
|
{
|
|
animFlags |= BONE_ANIM_OVERRIDE_FREEZE;
|
|
}
|
|
// animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps).
|
|
int firstFrame;
|
|
int lastFrame;
|
|
if ( animSpeed < 0 )
|
|
{//play anim backwards
|
|
|
|
lastFrame = animations[anim].firstFrame -1;
|
|
firstFrame = (animations[anim].numFrames -1) + animations[anim].firstFrame ;
|
|
}
|
|
else
|
|
{
|
|
firstFrame = animations[anim].firstFrame;
|
|
lastFrame = (animations[anim].numFrames) + animations[anim].firstFrame;
|
|
}
|
|
|
|
// first decide if we are doing an animation on the head already
|
|
// int startFrame, endFrame;
|
|
// const qboolean animatingHead = gi.G2API_GetAnimRangeIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone, &startFrame, &endFrame);
|
|
|
|
// if (!animatingHead || ( animations[anim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again
|
|
{
|
|
gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone,
|
|
firstFrame, lastFrame, animFlags, animSpeed, cg.time, -1, blendTime);
|
|
}
|
|
}
|
|
|
|
qboolean CG_G2PlayerHeadAnims( centity_t *cent )
|
|
{
|
|
if(!ValidAnimFileIndex(cent->gent->client->clientInfo.animFileIndex))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (cent->gent->faceBone == BONE_INDEX_INVALID)
|
|
{ // i don't have a face
|
|
return qfalse;
|
|
}
|
|
|
|
int anim = -1;
|
|
|
|
if ( cent->gent->health <= 0 )
|
|
{//Dead people close their eyes and don't make faces!
|
|
anim = FACE_DEAD;
|
|
}
|
|
else
|
|
{
|
|
if (!cent->gent->client->facial_blink)
|
|
{ // set the timers
|
|
cent->gent->client->facial_blink = cg.time + Q_flrand(4000.0, 8000.0);
|
|
cent->gent->client->facial_frown = cg.time + Q_flrand(6000.0, 10000.0);
|
|
cent->gent->client->facial_aux = cg.time + Q_flrand(6000.0, 10000.0);
|
|
}
|
|
|
|
//are we blinking?
|
|
if (cent->gent->client->facial_blink < 0)
|
|
{ // yes, check if we are we done blinking ?
|
|
if (-(cent->gent->client->facial_blink) < cg.time)
|
|
{ // yes, so reset blink timer
|
|
cent->gent->client->facial_blink = cg.time + Q_flrand(4000.0, 8000.0);
|
|
CG_G2SetHeadBlink( cent, qfalse ); //stop the blink
|
|
}
|
|
}
|
|
else // no we aren't blinking
|
|
{
|
|
if (cent->gent->client->facial_blink < cg.time)// but should we start ?
|
|
{
|
|
CG_G2SetHeadBlink( cent, qtrue );
|
|
if (cent->gent->client->facial_blink == 1)
|
|
{//requested to stay shut by SET_FACEEYESCLOSED
|
|
cent->gent->client->facial_blink = -(cg.time + 99999999.0f);// set blink timer
|
|
}
|
|
else
|
|
{
|
|
cent->gent->client->facial_blink = -(cg.time + 300.0f);// set blink timer
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (gi.VoiceVolume[cent->gent->s.clientNum] > 0) // if we aren't talking, then it will be 0, -1 for talking but paused
|
|
{
|
|
anim = FACE_TALK1 + gi.VoiceVolume[cent->gent->s.clientNum] -1;
|
|
}
|
|
else if (gi.VoiceVolume[cent->gent->s.clientNum] == 0) //don't do aux if in a slient part of speech
|
|
{//not talking
|
|
if (cent->gent->client->facial_aux < 0) // are we auxing ?
|
|
{ //yes
|
|
if (-(cent->gent->client->facial_aux) < cg.time)// are we done auxing ?
|
|
{ // yes, reset aux timer
|
|
cent->gent->client->facial_aux = cg.time + Q_flrand(7000.0, 10000.0);
|
|
}
|
|
else
|
|
{ // not yet, so choose aux
|
|
anim = FACE_ALERT;
|
|
}
|
|
}
|
|
else // no we aren't auxing
|
|
{ // but should we start ?
|
|
if (cent->gent->client->facial_aux < cg.time)
|
|
{//yes
|
|
anim = FACE_ALERT;
|
|
// set aux timer
|
|
cent->gent->client->facial_aux = -(cg.time + 2000.0);
|
|
}
|
|
}
|
|
|
|
if (anim != -1) //we we are auxing, see if we should override with a frown
|
|
{
|
|
if (cent->gent->client->facial_frown < 0)// are we frowning ?
|
|
{ // yes,
|
|
if (-(cent->gent->client->facial_frown) < cg.time)//are we done frowning ?
|
|
{ // yes, reset frown timer
|
|
cent->gent->client->facial_frown = cg.time + Q_flrand(7000.0, 10000.0);
|
|
}
|
|
else
|
|
{ // not yet, so choose frown
|
|
anim = FACE_FROWN;
|
|
}
|
|
}
|
|
else// no we aren't frowning
|
|
{ // but should we start ?
|
|
if (cent->gent->client->facial_frown < cg.time)
|
|
{
|
|
anim = FACE_FROWN;
|
|
// set frown timer
|
|
cent->gent->client->facial_frown = -(cg.time + 2000.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
}//talking
|
|
}//dead
|
|
if (anim != -1)
|
|
{
|
|
CG_G2SetHeadAnim( cent, anim );
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
CG_PlayerHeadExtension
|
|
-------------------------
|
|
*/
|
|
int CG_PlayerHeadExtension( centity_t *cent, refEntity_t *head )
|
|
{
|
|
clientInfo_t *ci = ¢->gent->client->clientInfo;;
|
|
|
|
// if we have facial texture extensions, go get the sound override and add it to the face skin
|
|
// if we aren't talking, then it will be 0
|
|
if (ci->extensions && (gi.VoiceVolume[cent->gent->s.clientNum] > 0))
|
|
{//FIXME: When talking, look at talkTarget, if any
|
|
//ALSO: When talking, add a head bob/movement on syllables - when gi.VoiceVolume[] changes drastically
|
|
|
|
if ( cent->gent->health <= 0 )
|
|
{//Dead people close their eyes and don't make faces! They also tell no tales... BUM BUM BAHHHHHHH!
|
|
//Make them always blink and frown
|
|
head->customSkin = ci->headSkin + 3;
|
|
return qtrue;
|
|
}
|
|
|
|
head->customSkin = ci->headSkin + 4+gi.VoiceVolume[cent->gent->s.clientNum];
|
|
//reset the frown and blink timers
|
|
}
|
|
else
|
|
// ok, we have facial extensions, but we aren't speaking. Lets decide if we need to frown or blink
|
|
if (ci->extensions)
|
|
{
|
|
int add_in = 0;
|
|
|
|
// deal with blink first
|
|
|
|
//Dead people close their eyes and don't make faces! They also tell no tales... BUM BUM BAHHHHHHH!
|
|
if ( cent->gent->health <= 0 )
|
|
{
|
|
//Make them always blink and frown
|
|
head->customSkin = ci->headSkin + 3;
|
|
return qtrue;
|
|
}
|
|
|
|
if (!cent->gent->client->facial_blink)
|
|
{ // reset blink timer
|
|
cent->gent->client->facial_blink = cg.time + Q_flrand(3000.0, 5000.0);
|
|
cent->gent->client->facial_frown = cg.time + Q_flrand(6000.0, 10000.0);
|
|
cent->gent->client->facial_aux = cg.time + Q_flrand(6000.0, 10000.0);
|
|
}
|
|
|
|
|
|
// now deal with auxing
|
|
// are we frowning ?
|
|
if (cent->gent->client->facial_aux < 0)
|
|
{
|
|
// are we done frowning ?
|
|
if (-(cent->gent->client->facial_aux) < cg.time)
|
|
{
|
|
// reset frown timer
|
|
cent->gent->client->facial_aux = cg.time + Q_flrand(6000.0, 10000.0);
|
|
}
|
|
else
|
|
{
|
|
// yes so set offset to frown
|
|
add_in = 4;
|
|
}
|
|
}
|
|
// no we aren't frowning
|
|
else
|
|
{
|
|
// but should we start ?
|
|
if (cent->gent->client->facial_aux < cg.time)
|
|
{
|
|
add_in = 4;
|
|
// set blink timer
|
|
cent->gent->client->facial_aux = -(cg.time + 3000.0);
|
|
}
|
|
}
|
|
|
|
// now, if we aren't auxing - lets see if we should be blinking or frowning
|
|
if (!add_in)
|
|
{
|
|
if( gi.VoiceVolume[cent->gent->s.clientNum] == -1 )
|
|
{//then we're talking and don't want to use blinking normal frames, force open eyes.
|
|
add_in = 0;
|
|
// reset blink timer
|
|
cent->gent->client->facial_blink = cg.time + Q_flrand(3000.0, 5000.0);
|
|
}
|
|
// are we blinking ?
|
|
else if (cent->gent->client->facial_blink < 0)
|
|
{
|
|
|
|
// yes so set offset to blink
|
|
add_in = 1;
|
|
|
|
// are we done blinking ?
|
|
if (-(cent->gent->client->facial_blink) < cg.time)
|
|
{
|
|
add_in = 0;
|
|
// reset blink timer
|
|
cent->gent->client->facial_blink = cg.time + Q_flrand(3000.0, 5000.0);
|
|
}
|
|
}
|
|
// no we aren't blinking
|
|
else
|
|
{
|
|
// but should we start ?
|
|
if (cent->gent->client->facial_blink < cg.time)
|
|
{
|
|
add_in = 1;
|
|
// set blink timer
|
|
cent->gent->client->facial_blink = -(cg.time + 200.0);
|
|
}
|
|
}
|
|
|
|
// now deal with frowning
|
|
// are we frowning ?
|
|
if (cent->gent->client->facial_frown < 0)
|
|
{
|
|
|
|
// yes so set offset to frown
|
|
add_in += 2;
|
|
|
|
// are we done frowning ?
|
|
if (-(cent->gent->client->facial_frown) < cg.time)
|
|
{
|
|
add_in -= 2;
|
|
// reset frown timer
|
|
cent->gent->client->facial_frown = cg.time + Q_flrand(6000.0, 10000.0);
|
|
}
|
|
}
|
|
// no we aren't frowning
|
|
else
|
|
{
|
|
// but should we start ?
|
|
if (cent->gent->client->facial_frown < cg.time)
|
|
{
|
|
add_in += 2;
|
|
// set blink timer
|
|
cent->gent->client->facial_frown = -(cg.time + 3000.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add in whatever we should
|
|
head->customSkin = ci->headSkin + add_in;
|
|
}
|
|
// at this point, we don't have any facial extensions, so who cares ?
|
|
else
|
|
{
|
|
head->customSkin = ci->headSkin;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
// CG_GetTagWorldPosition
|
|
//
|
|
// Can pass in NULL for the axis
|
|
//--------------------------------------------------------------
|
|
void CG_GetTagWorldPosition( refEntity_t *model, char *tag, vec3_t pos, vec3_t axis[3] )
|
|
{
|
|
orientation_t orientation;
|
|
|
|
// Get the requested tag
|
|
cgi_R_LerpTag( &orientation, model->hModel, model->oldframe, model->frame,
|
|
1.0f - model->backlerp, tag );
|
|
|
|
VectorCopy( model->origin, pos );
|
|
for ( int i = 0 ; i < 3 ; i++ )
|
|
{
|
|
VectorMA( pos, orientation.origin[i], model->axis[i], pos );
|
|
}
|
|
|
|
if ( axis )
|
|
{
|
|
MatrixMultiply( orientation.axis, model->axis, axis );
|
|
}
|
|
}
|
|
|
|
static qboolean calcedMp = qfalse;
|
|
|
|
/*
|
|
-------------------------
|
|
CG_GetPlayerLightLevel
|
|
-------------------------
|
|
*/
|
|
|
|
void CG_GetPlayerLightLevel( centity_t *cent )
|
|
{
|
|
vec3_t ambient={0}, directed, lightDir;
|
|
|
|
//Poll the renderer for the light level
|
|
if ( cent->currentState.clientNum == cg.snap->ps.clientNum )
|
|
{//hAX0R
|
|
ambient[0] = 666;
|
|
}
|
|
cgi_R_GetLighting( cent->lerpOrigin, ambient, directed, lightDir );
|
|
|
|
//Get the maximum value for the player
|
|
cent->gent->lightLevel = directed[0];
|
|
|
|
if ( directed[1] > cent->gent->lightLevel )
|
|
cent->gent->lightLevel = directed[1];
|
|
|
|
if ( directed[2] > cent->gent->lightLevel )
|
|
cent->gent->lightLevel = directed[2];
|
|
|
|
if ( cent->gent->client->ps.weapon == WP_SABER && cent->gent->client->ps.saberLength > 0 )
|
|
{
|
|
cent->gent->lightLevel += (cent->gent->client->ps.saberLength/cent->gent->client->ps.saberLengthMax)*200;
|
|
}
|
|
}
|
|
|
|
int CG_SaberHumSoundForEnt( gentity_t *gent )
|
|
{
|
|
//now: based on saber type and npc, pick correct hum sound
|
|
int saberHumSound = cgi_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" );
|
|
if ( gent && gent->client )
|
|
{
|
|
if ( gent->client->NPC_class == CLASS_DESANN )
|
|
{//desann
|
|
saberHumSound = cgi_S_RegisterSound( "sound/weapons/saber/saberhum2.wav" );
|
|
}
|
|
else if ( gent->client->NPC_class == CLASS_LUKE )
|
|
{//luke
|
|
saberHumSound = cgi_S_RegisterSound( "sound/weapons/saber/saberhum5.wav" );
|
|
}
|
|
else if ( gent->client->NPC_class == CLASS_KYLE )
|
|
{//Kyle NPC and player
|
|
saberHumSound = cgi_S_RegisterSound( "sound/weapons/saber/saberhum4.wav" );
|
|
}
|
|
else if ( gent->client->playerTeam == TEAM_ENEMY )//NPC_class == CLASS_TAVION )
|
|
{//reborn, shadowtroopers, tavion
|
|
saberHumSound = cgi_S_RegisterSound( "sound/weapons/saber/saberhum3.wav" );
|
|
}
|
|
else
|
|
{//allies
|
|
//use default
|
|
//saberHumSound = cgi_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" );
|
|
}
|
|
}
|
|
return saberHumSound;
|
|
}
|
|
/*
|
|
===============
|
|
CG_StopWeaponSounds
|
|
|
|
Stops any weapon sounds as needed
|
|
===============
|
|
*/
|
|
void CG_StopWeaponSounds( centity_t *cent )
|
|
{
|
|
qboolean weak = qfalse;
|
|
weaponInfo_t *weapon = &cg_weapons[ cent->currentState.weapon ];
|
|
|
|
if ( cent->currentState.weapon == WP_SABER )
|
|
{
|
|
if ( cent->gent && cent->gent->client && ( cent->currentState.saberInFlight || !cent->gent->client->ps.saberActive/*!cent->currentState.saberActive*/ ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
cgi_S_AddLoopingSound( cent->currentState.number,
|
|
cent->lerpOrigin,
|
|
vec3_origin,
|
|
CG_SaberHumSoundForEnt( &g_entities[cent->currentState.clientNum] ) );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.weapon == WP_STUN_BATON )
|
|
{
|
|
cgi_S_AddLoopingSound( cent->currentState.number,
|
|
cent->lerpOrigin,
|
|
vec3_origin,
|
|
weapon->firingSound );
|
|
return;
|
|
}
|
|
|
|
if ( !( cent->currentState.eFlags & EF_FIRING ) )
|
|
{
|
|
if ( cent->pe.lightningFiring )
|
|
{
|
|
if ( weapon->stopSound )
|
|
{
|
|
cgi_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->stopSound );
|
|
}
|
|
|
|
cent->pe.lightningFiring = qfalse;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( cent->gent && cent->gent->client )
|
|
{
|
|
if ( cent->gent->client->ps.ammo[AMMO_FORCE] < 1 )
|
|
weak = qtrue;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_ALT_FIRING && !weak )
|
|
{
|
|
if ( weapon->altFiringSound )
|
|
{
|
|
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->altFiringSound );
|
|
}
|
|
cent->pe.lightningFiring = qtrue;
|
|
}
|
|
else if ( cent->currentState.eFlags & EF_FIRING )
|
|
{
|
|
cent->pe.lightningFiring = qtrue;
|
|
if ( weapon->firingSound )
|
|
{
|
|
// Weak phaser sound should stutter
|
|
if ( weak && (rand() & 1) )
|
|
return;
|
|
|
|
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------- SABER STUFF --------
|
|
extern void CG_Smoke( vec3_t origin, vec3_t dir, float radius, float speed, qhandle_t shader, int flags);
|
|
|
|
void CG_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, saber_colors_t color, int rfx )
|
|
{
|
|
vec3_t mid, rgb={1,1,1};
|
|
qhandle_t blade = 0, glow = 0;
|
|
refEntity_t saber;
|
|
float radiusmult;
|
|
|
|
if ( length < 0.5f )
|
|
{
|
|
// if the thing is so short, just forget even adding me.
|
|
return;
|
|
}
|
|
|
|
// Find the midpoint of the saber for lighting purposes
|
|
VectorMA( origin, length * 0.5f, dir, mid );
|
|
|
|
switch( color )
|
|
{
|
|
case SABER_RED:
|
|
glow = cgs.media.redSaberGlowShader;
|
|
blade = cgs.media.redSaberCoreShader;
|
|
VectorSet( rgb, 1.0f, 0.2f, 0.2f );
|
|
break;
|
|
case SABER_ORANGE:
|
|
glow = cgs.media.orangeSaberGlowShader;
|
|
blade = cgs.media.orangeSaberCoreShader;
|
|
VectorSet( rgb, 1.0f, 0.5f, 0.1f );
|
|
break;
|
|
case SABER_YELLOW:
|
|
glow = cgs.media.yellowSaberGlowShader;
|
|
blade = cgs.media.yellowSaberCoreShader;
|
|
VectorSet( rgb, 1.0f, 1.0f, 0.2f );
|
|
break;
|
|
case SABER_GREEN:
|
|
glow = cgs.media.greenSaberGlowShader;
|
|
blade = cgs.media.greenSaberCoreShader;
|
|
VectorSet( rgb, 0.2f, 1.0f, 0.2f );
|
|
break;
|
|
case SABER_BLUE:
|
|
glow = cgs.media.blueSaberGlowShader;
|
|
blade = cgs.media.blueSaberCoreShader;
|
|
VectorSet( rgb, 0.2f, 0.4f, 1.0f );
|
|
break;
|
|
case SABER_PURPLE:
|
|
glow = cgs.media.purpleSaberGlowShader;
|
|
blade = cgs.media.purpleSaberCoreShader;
|
|
VectorSet( rgb, 0.9f, 0.2f, 1.0f );
|
|
break;
|
|
}
|
|
|
|
// always add a light because sabers cast a nice glow before they slice you in half!! or something...
|
|
cgi_R_AddLightToScene( mid, (length*2.0f) + (Q_flrand(0.0f, 1.0f)*8.0f), rgb[0], rgb[1], rgb[2] );
|
|
|
|
memset( &saber, 0, sizeof( refEntity_t ));
|
|
|
|
// Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many
|
|
// refEnts to do each glow blob individually
|
|
saber.saberLength = length;
|
|
|
|
// Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed.
|
|
// It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat
|
|
if (length < lengthMax )
|
|
{
|
|
radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5.
|
|
}
|
|
else
|
|
{
|
|
radiusmult = 1.0;
|
|
}
|
|
|
|
|
|
saber.radius = (2.8 + Q_flrand(-1.0f, 1.0f) * 0.2f)*radiusmult;
|
|
|
|
|
|
VectorCopy( origin, saber.origin );
|
|
VectorCopy( dir, saber.axis[0] );
|
|
saber.reType = RT_SABER_GLOW;
|
|
saber.customShader = glow;
|
|
saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff;
|
|
saber.renderfx = rfx;
|
|
|
|
cgi_R_AddRefEntityToScene( &saber );
|
|
|
|
// Do the hot core
|
|
VectorMA( origin, length, dir, saber.origin );
|
|
VectorMA( origin, -1, dir, saber.oldorigin );
|
|
saber.customShader = blade;
|
|
saber.reType = RT_LINE;
|
|
saber.radius = (1.0 + Q_flrand(-1.0f, 1.0f) * 0.2f)*radiusmult;
|
|
|
|
cgi_R_AddRefEntityToScene( &saber );
|
|
}
|
|
|
|
#define MAX_MARK_FRAGMENTS 256
|
|
#define MAX_MARK_POINTS 768
|
|
extern markPoly_t *CG_AllocMark();
|
|
|
|
void CG_CreateSaberMarks( vec3_t start, vec3_t end, vec3_t normal )
|
|
{
|
|
// byte colors[4];
|
|
int i, j, numFragments;
|
|
vec3_t axis[3], originalPoints[4], mid;
|
|
vec3_t markPoints[MAX_MARK_POINTS], projection;
|
|
polyVert_t *v, verts[MAX_VERTS_ON_POLY];
|
|
markPoly_t *mark;
|
|
markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf;
|
|
|
|
if ( !cg_addMarks.integer ) {
|
|
return;
|
|
}
|
|
|
|
float radius = 0.65f;
|
|
|
|
VectorSubtract( end, start, axis[1] );
|
|
VectorNormalize( axis[1] );
|
|
|
|
// create the texture axis
|
|
VectorCopy( normal, axis[0] );
|
|
CrossProduct( axis[1], axis[0], axis[2] );
|
|
|
|
// create the full polygon that we'll project
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
originalPoints[0][i] = start[i] - radius * axis[1][i] - radius * axis[2][i];
|
|
originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i];
|
|
originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i];
|
|
originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i];
|
|
}
|
|
|
|
VectorScale( normal, -1, projection );
|
|
|
|
// get the fragments
|
|
numFragments = cgi_CM_MarkFragments( 4, (const float (*)[3])originalPoints,
|
|
projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments );
|
|
|
|
|
|
for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ )
|
|
{
|
|
// we have an upper limit on the complexity of polygons that we store persistantly
|
|
if ( mf->numPoints > MAX_VERTS_ON_POLY )
|
|
{
|
|
mf->numPoints = MAX_VERTS_ON_POLY;
|
|
}
|
|
|
|
for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ )
|
|
{
|
|
vec3_t delta;
|
|
|
|
// Set up our texture coords, this may need some work
|
|
VectorCopy( markPoints[mf->firstPoint + j], v->xyz );
|
|
VectorAdd( end, start, mid );
|
|
VectorScale( mid, 0.5f, mid );
|
|
VectorSubtract( v->xyz, mid, delta );
|
|
|
|
v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * (0.05f + Q_flrand(0.0f, 1.0f) * 0.03f);
|
|
v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.15f + Q_flrand(0.0f, 1.0f) * 0.05f);
|
|
}
|
|
|
|
// save it persistantly, do burn first
|
|
mark = CG_AllocMark();
|
|
mark->time = cg.time;
|
|
mark->alphaFade = qtrue;
|
|
mark->markShader = cgs.media.rivetMarkShader;
|
|
mark->poly.numVerts = mf->numPoints;
|
|
mark->color[0] = mark->color[1] = mark->color[2] = mark->color[3] = 255;
|
|
memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
|
|
|
|
// And now do a glow pass
|
|
// by moving the start time back, we can hack it to fade out way before the burn does
|
|
mark = CG_AllocMark();
|
|
mark->time = cg.time - 8500;
|
|
mark->alphaFade = qfalse;
|
|
mark->markShader = cgi_R_RegisterShader("gfx/effects/saberDamageGlow" );
|
|
mark->poly.numVerts = mf->numPoints;
|
|
mark->color[0] = 215 + Q_flrand(0.0f, 1.0f) * 40.0f;
|
|
mark->color[1] = 96 + Q_flrand(0.0f, 1.0f) * 32.0f;
|
|
mark->color[2] = mark->color[3] = Q_flrand(0.0f, 1.0f)*15.0f;
|
|
memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
|
|
}
|
|
}
|
|
|
|
extern void FX_AddPrimitive( CEffect **effect, int killTime );
|
|
//-------------------------------------------------------
|
|
void CG_CheckSaberInWater( centity_t *cent, centity_t *scent, int modelIndex, vec3_t origin, vec3_t angles )
|
|
{
|
|
gclient_t *client = cent->gent->client;
|
|
vec3_t saberOrg;
|
|
if ( !client )
|
|
{
|
|
return;
|
|
}
|
|
if ( !scent ||
|
|
modelIndex == -1 ||
|
|
scent->gent->ghoul2.size() <= modelIndex ||
|
|
scent->gent->ghoul2[modelIndex].mModelindex == -1 )
|
|
{
|
|
return;
|
|
}
|
|
mdxaBone_t boltMatrix;
|
|
|
|
// figure out where the actual model muzzle is
|
|
gi.G2API_GetBoltMatrix( scent->gent->ghoul2, modelIndex, 0, &boltMatrix, angles, origin, cg.time, cgs.model_draw, scent->currentState.modelScale );
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, saberOrg );
|
|
|
|
int contents = gi.pointcontents( saberOrg, cent->currentState.clientNum );
|
|
if ( (contents&CONTENTS_WATER) || (contents&CONTENTS_SLIME) )
|
|
{//still in water
|
|
client->ps.saberEventFlags |= SEF_INWATER;
|
|
return;
|
|
}
|
|
//not in water
|
|
client->ps.saberEventFlags &= ~SEF_INWATER;
|
|
}
|
|
|
|
void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, vec3_t origin, vec3_t angles)
|
|
{
|
|
vec3_t org_, end,//org_future,
|
|
axis_[3] = {{0,0,0}, {0,0,0}, {0,0,0}};//, axis_future[3]={{0,0,0}, {0,0,0}, {0,0,0}}; // shut the compiler up
|
|
trace_t trace;
|
|
float length;
|
|
|
|
gclient_t *client = cent->gent->client;
|
|
|
|
if ( !client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (vr->item_selector)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
Ghoul2 Insert Start
|
|
*/
|
|
|
|
// if (scent->gent->ghoul2.size())
|
|
if(1)
|
|
{
|
|
if ( !scent ||
|
|
modelIndex == -1 ||
|
|
scent->gent->ghoul2.size() <= modelIndex ||
|
|
scent->gent->ghoul2[modelIndex].mModelindex == -1 )
|
|
{
|
|
return;
|
|
}
|
|
mdxaBone_t boltMatrix;
|
|
|
|
// figure out where the actual model muzzle is
|
|
gi.G2API_GetBoltMatrix(scent->gent->ghoul2, modelIndex, 0, &boltMatrix, angles, origin, cg.time, cgs.model_draw, scent->currentState.modelScale);
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, org_);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_X, axis_[0]);//was NEGATIVE_Y, but the md3->glm exporter screws up this tag somethin' awful
|
|
if (!cent->gent->client->ps.saberInFlight && CG_getPlayer1stPersonSaber(cent))
|
|
{
|
|
vec3_t angles;
|
|
BG_CalculateVRSaberPosition(org_, angles);
|
|
AnglesToAxis(angles, axis_);
|
|
VectorMA(org_, 4.0f, axis_[0], org_);
|
|
}
|
|
|
|
//Now figure out where this info will be next frame
|
|
/*
|
|
{
|
|
vec3_t futureOrigin, futureAngles, orgDiff, angDiff;
|
|
int futuretime;
|
|
|
|
//futuretime = (int)((cg.time + 99)/50) * 50;
|
|
futuretime = cg.time+100;
|
|
|
|
VectorCopy( angles, futureAngles );
|
|
VectorCopy( origin, futureOrigin );
|
|
|
|
//note: for a thrown saber, this does nothing, really
|
|
if ( cent->gent )
|
|
{
|
|
VectorSubtract( cent->lerpOrigin, cent->gent->lastOrigin, orgDiff );
|
|
VectorSubtract( cent->lerpAngles, cent->gent->lastAngles, angDiff );
|
|
VectorAdd( futureOrigin, orgDiff, futureOrigin );
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
futureAngles[i] = AngleNormalize360( futureAngles[i]+angDiff[i] );
|
|
}
|
|
}
|
|
|
|
// figure out where the actual model muzzle will be after next server frame.
|
|
gi.G2API_GetBoltMatrix(scent->gent->ghoul2, modelIndex, 0, &boltMatrix, futureAngles, futureOrigin, futuretime, cgs.model_draw, scent->currentState.modelScale);
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, org_future);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_X, axis_future[0]);//was NEGATIVE_Y, but the md3->glm exporter screws up this tag somethin' awful
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
CG_GetTagWorldPosition( saber, "tag_flash", org_, axis_ );
|
|
}
|
|
|
|
/*
|
|
Ghoul2 Insert End
|
|
*/
|
|
|
|
//store where saber is this frame
|
|
VectorCopy( org_, cent->gent->client->renderInfo.muzzlePoint );
|
|
VectorCopy( axis_[0], cent->gent->client->renderInfo.muzzleDir );
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
//length for purposes of rendering and marks trace will be longer than blade so we don't damage past a wall's surface
|
|
if ( cent->gent->client->ps.saberLength < cent->gent->client->ps.saberLengthMax )
|
|
{
|
|
if ( cent->gent->client->ps.saberLength < cent->gent->client->ps.saberLengthMax - 8 )
|
|
{
|
|
length = cent->gent->client->ps.saberLength + 8;
|
|
}
|
|
else
|
|
{
|
|
length = cent->gent->client->ps.saberLengthMax;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
length = cent->gent->client->ps.saberLength;
|
|
}
|
|
VectorMA( org_, length, axis_[0], end );
|
|
|
|
// Now store where the saber will be after next frame.
|
|
//VectorCopy(org_future, cent->gent->client->renderInfo.muzzlePointNext);
|
|
//VectorCopy(axis_future[0], cent->gent->client->renderInfo.muzzleDirNext);
|
|
|
|
VectorAdd( end, axis_[0], end );
|
|
|
|
// If the saber is in flight we shouldn't trace from the player to the muzzle point
|
|
if ( cent->currentState.saberInFlight )
|
|
{
|
|
trace.fraction = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
gi.trace( &trace, cent->lerpOrigin, NULL, NULL, cent->gent->client->renderInfo.muzzlePoint, cent->currentState.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0 );
|
|
}
|
|
|
|
if ( trace.fraction < 1.0f )
|
|
{
|
|
// Saber is on the other side of a wall
|
|
cent->gent->client->ps.saberLength = 0.1f;
|
|
cent->gent->client->ps.saberEventFlags &= ~SEF_INWATER;
|
|
}
|
|
else
|
|
{
|
|
for ( int i = 0; i < 1; i++ )//was 2 because it would go through architecture and leave saber trails on either side of the brush - but still looks bad if we hit a corner, blade is still 8 longer than hit
|
|
{
|
|
if ( i )
|
|
{//tracing from end to base
|
|
//cgi_CM_BoxTrace( &trace, end, org_, NULL, NULL, 0, MASK_SHOT );
|
|
gi.trace( &trace, end, NULL, NULL, org_, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0 );
|
|
}
|
|
else
|
|
{//tracing from base to end
|
|
//cgi_CM_BoxTrace( &trace, end, org_, NULL, NULL, 0, MASK_SHOT );
|
|
gi.trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID|CONTENTS_WATER|CONTENTS_SLIME, G2_NOCOLLIDE, 0 );
|
|
}
|
|
|
|
if ( trace.fraction < 1.0f )
|
|
{
|
|
if ( (trace.contents&CONTENTS_WATER) || (trace.contents&CONTENTS_SLIME) )
|
|
{
|
|
/*
|
|
if ( !(cent->gent->client->ps.saberEventFlags&SEF_INWATER) )
|
|
{
|
|
}
|
|
*/
|
|
if ( !Q_irand( 0, 10 ) )
|
|
{//FIXME: don't do this this way.... :)
|
|
vec3_t spot;
|
|
VectorCopy( trace.endpos, spot );
|
|
spot[2] += 4;
|
|
G_PlayEffect( "saber/boil", spot );
|
|
cgi_S_StartSound ( spot, -1, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/saber/hitwater.wav" ) );
|
|
}
|
|
//cent->gent->client->ps.saberEventFlags |= SEF_INWATER;
|
|
//don't do other trace
|
|
i = 1;
|
|
}
|
|
else
|
|
{
|
|
theFxScheduler.PlayEffect( "spark", trace.endpos, trace.plane.normal );
|
|
// All I need is a bool to mark whether I have a previous point to work with.
|
|
//....come up with something better..
|
|
if ( client->saberTrail.haveOldPos[i] )
|
|
{
|
|
if ( trace.entityNum == ENTITYNUM_WORLD )
|
|
{//only put marks on architecture
|
|
// Let's do some cool burn/glowing mark bits!!!
|
|
CG_CreateSaberMarks( client->saberTrail.oldPos[i], trace.endpos, trace.plane.normal );
|
|
|
|
//make a sound
|
|
if ( cg.time - cent->gent->client->ps.saberHitWallSoundDebounceTime >= 100 )
|
|
{//ugh, need to have a real sound debouncer... or do this game-side
|
|
cent->gent->client->ps.saberHitWallSoundDebounceTime = cg.time;
|
|
cgi_S_StartSound ( cent->lerpOrigin, cent->currentState.clientNum, CHAN_ITEM, cgi_S_RegisterSound( va ( "sound/weapons/saber/saberhitwall%d.wav", Q_irand( 1, 3 ) ) ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we impact next frame, we'll mark a slash mark
|
|
client->saberTrail.haveOldPos[i] = qtrue;
|
|
}
|
|
}
|
|
|
|
// stash point so we can connect-the-dots later
|
|
VectorCopy( trace.endpos, client->saberTrail.oldPos[i] );
|
|
VectorCopy( trace.plane.normal, client->saberTrail.oldNormal[i] );
|
|
|
|
if ( !i )
|
|
{
|
|
//Now that we don't let the blade go through walls, we need to shorten the blade when it hits one
|
|
cent->gent->client->ps.saberLength = cent->gent->client->ps.saberLength * trace.fraction;//this will stop damage from going through walls
|
|
if ( cent->gent->client->ps.saberLength <= 0.1f )
|
|
{//SIGH... hack so it doesn't play the saber turn-on sound that plays when you first turn the saber on (assumed when saber is active but length is zero)
|
|
cent->gent->client->ps.saberLength = 0.1f;//FIXME: may go through walls still??
|
|
}
|
|
//FIXME: should probably re-extend instantly, not use the "turning-on" growth rate
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cent->gent->client->ps.saberEventFlags &= ~SEF_INWATER;
|
|
if ( client->saberTrail.haveOldPos[i] )
|
|
{
|
|
// Hmmm, no impact this frame, but we have an old point
|
|
// Let's put the mark there, we should use an endcap mark to close the line, but we
|
|
// can probably just get away with a round mark
|
|
//CG_ImpactMark( cgs.media.rivetMarkShader, client->saberTrail.oldPos[i], client->saberTrail.oldNormal[i],
|
|
// 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse );
|
|
}
|
|
|
|
// we aren't impacting, so turn off our mark tracking mechanism
|
|
client->saberTrail.haveOldPos[i] = qfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
saberTrail_t *saberTrail = &client->saberTrail;
|
|
|
|
#define SABER_TRAIL_TIME 60.0f
|
|
|
|
// if we happen to be timescaled or running in a high framerate situation, we don't want to flood
|
|
// the system with very small trail slices...but perhaps doing it by distance would yield better results?
|
|
if ( saberTrail->lastTime > cg.time )
|
|
{//after a pause, cg.time jumps ahead in time for one frame
|
|
//and lastTime gets set to that and will freak out, so, since
|
|
//it's never valid for saberTrail->lastTime to be > cg.time,
|
|
//cap it to cg.time here
|
|
saberTrail->lastTime = cg.time;
|
|
}
|
|
if ( cg.time > saberTrail->lastTime + 2 && saberTrail->inAction) // 2ms
|
|
{
|
|
//Swinging the saber quick enough to trigger a sound should also invoke trails
|
|
if ( saberTrail->inAction && cg.time < saberTrail->lastTime + 300 ) // if we have a stale segment, don't draw until we have a fresh one
|
|
{
|
|
vec3_t rgb1={255,255,255};
|
|
|
|
switch( client->ps.saberColor )
|
|
{
|
|
case SABER_RED:
|
|
VectorSet( rgb1, 255.0f, 0.0f, 0.0f );
|
|
break;
|
|
case SABER_ORANGE:
|
|
VectorSet( rgb1, 255.0f, 64.0f, 0.0f );
|
|
break;
|
|
case SABER_YELLOW:
|
|
VectorSet( rgb1, 255.0f, 255.0f, 0.0f );
|
|
break;
|
|
case SABER_GREEN:
|
|
VectorSet( rgb1, 0.0f, 255.0f, 0.0f );
|
|
break;
|
|
case SABER_BLUE:
|
|
VectorSet( rgb1, 0.0f, 64.0f, 255.0f );
|
|
break;
|
|
case SABER_PURPLE:
|
|
VectorSet( rgb1, 220.0f, 0.0f, 255.0f );
|
|
break;
|
|
}
|
|
|
|
float diff = cg.time - saberTrail->lastTime;
|
|
|
|
// I'm not sure that clipping this is really the best idea
|
|
if ( diff <= SABER_TRAIL_TIME * 2 )
|
|
{
|
|
float oldAlpha = 1.0f - ( diff / SABER_TRAIL_TIME );
|
|
|
|
// build a quad
|
|
CTrail *fx = new CTrail;
|
|
|
|
// Go from new muzzle to new end...then to old end...back down to old muzzle...finally
|
|
// connect back to the new muzzle...this is our trail quad
|
|
VectorCopy( org_, fx->mVerts[0].origin );
|
|
VectorMA( end, 3.0f, axis_[0], fx->mVerts[1].origin );
|
|
|
|
VectorCopy( saberTrail->tip, fx->mVerts[2].origin );
|
|
VectorCopy( saberTrail->base, fx->mVerts[3].origin );
|
|
|
|
// New muzzle
|
|
VectorCopy( rgb1, fx->mVerts[0].rgb );
|
|
fx->mVerts[0].alpha = 255.0f;
|
|
|
|
fx->mVerts[0].ST[0] = 0.0f;
|
|
fx->mVerts[0].ST[1] = 0.99f;
|
|
fx->mVerts[0].destST[0] = 0.99f;
|
|
fx->mVerts[0].destST[1] = 0.99f;
|
|
|
|
// new tip
|
|
VectorCopy( rgb1, fx->mVerts[1].rgb );
|
|
fx->mVerts[1].alpha = 255.0f;
|
|
|
|
fx->mVerts[1].ST[0] = 0.0f;
|
|
fx->mVerts[1].ST[1] = 0.0f;
|
|
fx->mVerts[1].destST[0] = 0.99f;
|
|
fx->mVerts[1].destST[1] = 0.0f;
|
|
|
|
// old tip
|
|
VectorCopy( rgb1, fx->mVerts[2].rgb );
|
|
fx->mVerts[2].alpha = 255.0f;
|
|
|
|
fx->mVerts[2].ST[0] = 0.99f - oldAlpha; // NOTE: this just happens to contain the value I want
|
|
fx->mVerts[2].ST[1] = 0.0f;
|
|
fx->mVerts[2].destST[0] = 0.99f + fx->mVerts[2].ST[0];
|
|
fx->mVerts[2].destST[1] = 0.0f;
|
|
|
|
// old muzzle
|
|
VectorCopy( rgb1, fx->mVerts[3].rgb );
|
|
fx->mVerts[3].alpha = 255.0f;
|
|
|
|
fx->mVerts[3].ST[0] = 0.99f - oldAlpha; // NOTE: this just happens to contain the value I want
|
|
fx->mVerts[3].ST[1] = 0.99f;
|
|
fx->mVerts[3].destST[0] = 0.99f + fx->mVerts[2].ST[0];
|
|
fx->mVerts[3].destST[1] = 0.99f;
|
|
|
|
fx->mShader = cgs.media.saberBlurShader;
|
|
// fx->SetFlags( FX_USE_ALPHA );
|
|
FX_AddPrimitive( (CEffect**)&fx, SABER_TRAIL_TIME );
|
|
}
|
|
}
|
|
|
|
// we must always do this, even if we aren't active..otherwise we won't know where to pick up from
|
|
VectorCopy( org_, saberTrail->base );
|
|
VectorMA( end, 3.0f, axis_[0], saberTrail->tip );
|
|
saberTrail->lastTime = cg.time;
|
|
}
|
|
|
|
// Pass in the renderfx flags attached to the saber weapon model...this is done so that saber glows
|
|
// will get rendered properly in a mirror...not sure if this is necessary??
|
|
CG_DoSaber( org_, axis_[0], length, client->ps.saberLengthMax, client->ps.saberColor, renderfx );
|
|
}
|
|
|
|
//--------------- END SABER STUFF --------
|
|
|
|
/*
|
|
===============
|
|
CG_Player
|
|
|
|
FIXME: Extend this to be a render func for all multiobject entities in the game such that:
|
|
|
|
You can have and stack multiple animated pieces (not just legs and torso)
|
|
You can attach "bolt-ons" that either animate or don't (weapons, heads, borg pieces)
|
|
You can attach any object to any tag on any object (weapon on the head, etc.)
|
|
|
|
Basically, keep a list of objects:
|
|
Root object (in this case, the legs) with this info:
|
|
model
|
|
skin
|
|
effects
|
|
scale
|
|
if animated, frame or anim number
|
|
drawn at origin and angle of master entity
|
|
Animated objects, with this info:
|
|
model
|
|
skin
|
|
effects
|
|
scale
|
|
frame or anim number
|
|
object it's attached to
|
|
tag to attach it's tag_parent to
|
|
angle offset to attach it with
|
|
Non-animated objects, with this info:
|
|
model
|
|
skin
|
|
effects
|
|
scale
|
|
object it's attached to
|
|
tag to attach it's tag_parent to
|
|
angle offset to attach it with
|
|
|
|
ALSO:
|
|
Move the auto angle setting back up to the game
|
|
Implement 3-axis scaling
|
|
Implement alpha
|
|
Implement tint
|
|
Implement other effects (generic call effect at org and dir (or tag), with parms?)
|
|
|
|
===============
|
|
*/
|
|
extern qboolean G_ControlledByPlayer( gentity_t *self );
|
|
|
|
void CG_Player(centity_t *cent ) {
|
|
clientInfo_t *ci;
|
|
qboolean shadow, staticScale = qfalse;
|
|
float shadowPlane;
|
|
const weaponData_t *wData = NULL;
|
|
|
|
calcedMp = qfalse;
|
|
|
|
if ( cent->currentState.eFlags & EF_NODRAW )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//FIXME: make sure this thing has a gent and client
|
|
if(!cent->gent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!cent->gent->client)
|
|
{
|
|
return;
|
|
}
|
|
|
|
vr->weaponid = cg.snap->ps.weapon;
|
|
|
|
//Get the player's light level for stealth calculations
|
|
CG_GetPlayerLightLevel( cent );
|
|
|
|
if ((in_camera) && !(cent->currentState.eFlags & EF_NPC)) // If player in camera then no need for shadow
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(cent->currentState.number == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
|
|
{
|
|
calcedMp = qtrue;
|
|
}
|
|
|
|
ci = ¢->gent->client->clientInfo;
|
|
|
|
if ( !ci->infoValid )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Before we go any further, default this to false, it can be updated later
|
|
vr->velocitytriggered = false;
|
|
|
|
if ( cent->currentState.vehicleModel != 0 )
|
|
{//Using an alternate (md3 for now) model
|
|
refEntity_t ent;
|
|
vec3_t tempAngles = {0, 0, 0}, velocity = {0, 0, 0};
|
|
float speed;
|
|
memset (&ent, 0, sizeof(ent));
|
|
|
|
if ( !cg.renderingThirdPerson )
|
|
{
|
|
gi.SendConsoleCommand( "cg_thirdperson 1\n" );
|
|
return;
|
|
}
|
|
|
|
ent.renderfx = 0;
|
|
if ( cent->currentState.number == cg.snap->ps.clientNum )
|
|
{//player
|
|
if ( !cg.renderingThirdPerson )
|
|
{
|
|
ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
}
|
|
|
|
// add the shadow
|
|
shadow = CG_PlayerShadow( cent, &shadowPlane );
|
|
|
|
if ( (cg_shadows.integer == 2) || (cg_shadows.integer == 3 && shadow) )
|
|
{
|
|
ent.renderfx |= RF_SHADOW_PLANE;
|
|
}
|
|
ent.renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
|
|
if ( cent->gent->NPC && cent->gent->NPC->scriptFlags & SCF_MORELIGHT )
|
|
{
|
|
ent.renderfx |= RF_MORELIGHT; //bigger than normal min light
|
|
}
|
|
|
|
VectorCopy( cent->lerpOrigin, ent.origin );
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin );
|
|
|
|
VectorSet( tempAngles, 0, cent->lerpAngles[1], 0 );
|
|
VectorCopy( tempAngles, cg.refdefViewAngles );
|
|
|
|
tempAngles[0] = cent->lerpAngles[0];
|
|
if ( tempAngles[0] > 30 )
|
|
{
|
|
tempAngles[0] = 30;
|
|
}
|
|
else if ( tempAngles[0] < -30 )
|
|
{
|
|
tempAngles[0] = -30;
|
|
}
|
|
|
|
//FIXME: roll
|
|
VectorCopy( cent->gent->client->ps.velocity, velocity );
|
|
speed = VectorNormalize( velocity );
|
|
|
|
if ( speed )
|
|
{
|
|
vec3_t rt;
|
|
float side;
|
|
|
|
// Magic number fun! Speed is used for banking, so modulate the speed by a sine wave
|
|
speed *= sin( (cg.frametime + 200 ) * 0.003 );
|
|
|
|
// Clamp to prevent harsh rolling
|
|
if ( speed > 60 )
|
|
speed = 60;
|
|
|
|
AngleVectors( tempAngles, NULL, rt, NULL );
|
|
side = speed * DotProduct( velocity, rt );
|
|
tempAngles[2] -= side;
|
|
}
|
|
/*
|
|
tempAngles[2] = cent->lerpAngles[1];
|
|
if ( tempAngles[2] > 30 )
|
|
{
|
|
tempAngles[2] = 30;
|
|
}
|
|
else if ( tempAngles[2] < -30 )
|
|
{
|
|
tempAngles[2] = -30;
|
|
}
|
|
*/
|
|
|
|
AnglesToAxis( tempAngles, ent.axis );
|
|
ScaleModelAxis( &ent );
|
|
|
|
VectorCopy( ent.origin, ent.lightingOrigin );
|
|
|
|
//FIXME: what if it's a G2 model?
|
|
/*
|
|
if ghoul model
|
|
{
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
gi.G2API_GetBoltMatrix(cent->gent->ghoul2, cent->gent->playerModel, cent->gent->headBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale);
|
|
// update where we think the head is, and where the eyes point
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, cent->gent->client->renderInfo.eyePoint);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Y, tempAxis);//was POSITIVE_X
|
|
vectoangles( tempAxis, cent->gent->client->renderInfo.eyeAngles );
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
//FIXME: properly generate muzzlePoint
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.muzzlePoint );
|
|
AngleVectors( cent->lerpAngles, cent->gent->client->renderInfo.muzzleDir, NULL, NULL );
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.eyePoint );
|
|
VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.eyeAngles );
|
|
ent.hModel = cgs.model_draw[cent->currentState.vehicleModel];
|
|
}
|
|
cgi_R_AddRefEntityToScene( &ent );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.weapon )
|
|
{
|
|
wData = &weaponData[cent->currentState.weapon];
|
|
}
|
|
/*
|
|
Ghoul2 Insert Start
|
|
*/
|
|
// do this if we are a ghoul2 model
|
|
if (cent->gent->ghoul2.size())
|
|
// if (1)
|
|
{
|
|
refEntity_t ent;
|
|
vec3_t tempAngles;
|
|
memset (&ent, 0, sizeof(ent));
|
|
|
|
//FIXME: if at all possible, do all our sets before our gets to do only *1* G2 skeleton transform per render frame
|
|
CG_SetGhoul2Info(&ent, cent);
|
|
|
|
// Weapon sounds may need to be stopped, so check now
|
|
CG_StopWeaponSounds( cent );
|
|
|
|
// add powerups floating behind the player
|
|
CG_PlayerPowerups( cent );
|
|
|
|
// add the shadow
|
|
shadow = CG_PlayerShadow( cent, &shadowPlane );
|
|
|
|
// add a water splash if partially in and out of water
|
|
CG_PlayerSplash( cent );
|
|
|
|
// get the player model information
|
|
ent.renderfx = 0;
|
|
if ( !cg.renderingThirdPerson || cg.zoomMode )
|
|
{//in first person or zoomed in
|
|
if ( cg.snap->ps.viewEntity <= 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD)
|
|
{//no viewentity
|
|
if ( cent->currentState.number == cg.snap->ps.clientNum )
|
|
{//I am the player
|
|
if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
|
|
{//not using saber or fists
|
|
ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
}
|
|
}
|
|
else if ( cent->currentState.number == cg.snap->ps.viewEntity )
|
|
{//I am the view entity
|
|
if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
|
|
{//not using first person saber test or, if so, not using saber
|
|
ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( cent->gent->client->ps.powerups[PW_DISINT_2] > cg.time )
|
|
{//ghost!
|
|
ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
|
|
if ( (cg_shadows.integer == 2) || (cg_shadows.integer == 3 && shadow) )
|
|
{
|
|
ent.renderfx |= RF_SHADOW_PLANE;
|
|
}
|
|
ent.shadowPlane = shadowPlane;
|
|
ent.renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
|
|
if ( cent->gent->NPC && cent->gent->NPC->scriptFlags & SCF_MORELIGHT )
|
|
{
|
|
ent.renderfx |= RF_MORELIGHT; //bigger than normal min light
|
|
}
|
|
|
|
CG_RegisterWeapon( cent->currentState.weapon );
|
|
|
|
//---------------
|
|
if ( cent->currentState.eFlags & EF_LOCKED_TO_WEAPON && cent->gent && cent->gent->health > 0 && cent->gent->owner )
|
|
{
|
|
centity_t *chair = &cg_entities[cent->gent->owner->s.number];
|
|
if ( chair && chair->gent )
|
|
{
|
|
vec3_t temp;
|
|
mdxaBone_t boltMatrix;
|
|
|
|
// We'll set the turret yaw directly
|
|
VectorClear( chair->gent->s.apos.trBase );
|
|
VectorClear( temp );
|
|
|
|
chair->gent->s.apos.trBase[YAW] = cent->lerpAngles[YAW];
|
|
temp[PITCH] = -cent->lerpAngles[PITCH];
|
|
cent->lerpAngles[ROLL] = 0;
|
|
|
|
//NOTE: call this so it updates on the server and client
|
|
BG_G2SetBoneAngles( chair, chair->gent, chair->gent->lowerLumbarBone, temp, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, cgs.model_draw );
|
|
//gi.G2API_SetBoneAngles( &chair->gent->ghoul2[0], "swivel_bone", temp, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, cgs.model_draw );
|
|
VectorCopy( temp, chair->gent->lastAngles );
|
|
|
|
gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
|
|
|
|
// Getting the seat bolt here
|
|
gi.G2API_GetBoltMatrix( chair->gent->ghoul2, chair->gent->playerModel, chair->gent->headBolt,
|
|
&boltMatrix, chair->gent->s.apos.trBase, chair->gent->currentOrigin, cg.time,
|
|
cgs.model_draw, chair->currentState.modelScale );
|
|
// Storing ent position, bolt position, and bolt axis
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent.origin );
|
|
VectorCopy( ent.origin, chair->gent->pos2 );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, chair->gent->pos3 );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, chair->gent->pos4 );
|
|
|
|
VectorCopy( ent.origin, ent.oldorigin );
|
|
VectorCopy( ent.origin, ent.lightingOrigin );
|
|
|
|
// FIXME: Mike claims that hacking the eyepoint will make them shoot at me.......so,
|
|
// we move up from the seat bolt and store off that point.
|
|
// VectorMA( ent.origin, -20, chair->gent->pos3, cent->gent->client->renderInfo.eyePoint );
|
|
// VectorMA( cent->gent->client->renderInfo.eyePoint, 40, chair->gent->pos4, cent->gent->client->renderInfo.eyePoint );
|
|
|
|
AnglesToAxis( cent->lerpAngles, ent.axis );
|
|
VectorCopy( cent->lerpAngles, tempAngles);//tempAngles is needed a lot below
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//---------------
|
|
CG_G2PlayerAngles( cent, ent.axis, tempAngles);
|
|
//Deal with facial expressions
|
|
CG_G2PlayerHeadAnims( cent );
|
|
|
|
VectorCopy( cent->lerpOrigin, ent.origin);
|
|
if (ent.modelScale[2] && ent.modelScale[2] != 1.0f)
|
|
{
|
|
ent.origin[2] += 24 * (ent.modelScale[2] - 1);
|
|
}
|
|
VectorCopy( ent.origin, ent.oldorigin);
|
|
VectorCopy( ent.origin, ent.lightingOrigin );
|
|
}
|
|
|
|
if ( cent->gent && cent->gent->client )
|
|
{
|
|
cent->gent->client->ps.legsYaw = tempAngles[YAW];
|
|
}
|
|
ScaleModelAxis(&ent);
|
|
|
|
extern vmCvar_t cg_thirdPersonAlpha;
|
|
|
|
if ( (cent->gent->s.number == 0 || G_ControlledByPlayer( cent->gent )) )
|
|
{
|
|
float alpha = 1.0f;
|
|
if ( (cg.overrides.active&CG_OVERRIDE_3RD_PERSON_APH) )
|
|
{
|
|
alpha = cg.overrides.thirdPersonAlpha;
|
|
}
|
|
else
|
|
{
|
|
alpha = cg_thirdPersonAlpha.value;
|
|
}
|
|
|
|
if ( alpha < 1.0f )
|
|
{
|
|
ent.renderfx |= RF_ALPHA_FADE;
|
|
ent.shaderRGBA[3] = (unsigned char)(alpha * 255.0f);
|
|
}
|
|
}
|
|
|
|
if ( !cg.renderingThirdPerson
|
|
&& ( /*cg.snap->ps.weapon == WP_SABER || */cg.snap->ps.weapon == WP_MELEE )
|
|
&& !cent->gent->s.number )
|
|
{// Yeah um, this needs to not do this quite this way
|
|
ent.customSkin = cgi_R_RegisterSkin( "models/players/kyle/model_fpls2.skin" ); //precached in g_client.cpp
|
|
//turn on arm caps
|
|
gi.G2API_SetSurfaceOnOff( ¢->gent->ghoul2[cent->gent->playerModel], "r_arm_cap_torso_off", 0 );
|
|
gi.G2API_SetSurfaceOnOff( ¢->gent->ghoul2[cent->gent->playerModel], "l_arm_cap_torso_off", 0 );
|
|
}
|
|
else
|
|
{
|
|
ent.customSkin = 0;
|
|
//turn off arm caps
|
|
gi.G2API_SetSurfaceOnOff( ¢->gent->ghoul2[cent->gent->playerModel], "r_arm_cap_torso_off", 0x00000002 );
|
|
gi.G2API_SetSurfaceOnOff( ¢->gent->ghoul2[cent->gent->playerModel], "l_arm_cap_torso_off", 0x00000002 );
|
|
}
|
|
|
|
// We want to be able to do cool full-body type effects
|
|
CG_AddRefEntityWithPowerups( &ent, cent->currentState.powerups, cent );
|
|
|
|
if ( cg_debugBB.integer)
|
|
{
|
|
CG_CreateBBRefEnts(¢->currentState, ent.origin);
|
|
}
|
|
|
|
//Initialize all these to *some* valid data
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.headPoint );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.handRPoint );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.handLPoint );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.footRPoint );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.footLPoint );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.torsoPoint );
|
|
VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.torsoAngles );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.crotchPoint );
|
|
if ( cent->currentState.number != 0
|
|
|| cg.renderingThirdPerson
|
|
|| cg.snap->ps.stats[STAT_HEALTH] <= 0
|
|
|| ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )//First person saber
|
|
)
|
|
{//in some third person mode or NPC
|
|
//we don't override thes in pure 1st person because they will be set before this func
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.eyePoint );
|
|
VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.eyeAngles );
|
|
if ( !cent->gent->client->ps.saberInFlight )
|
|
{
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.muzzlePoint );
|
|
VectorCopy( ent.axis[0], cent->gent->client->renderInfo.muzzleDir );
|
|
|
|
if ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE))
|
|
{
|
|
vec3_t angles;
|
|
BG_CalculateVRSaberPosition(cent->gent->client->renderInfo.muzzlePoint, angles);
|
|
AngleVectors( angles, cent->gent->client->renderInfo.muzzleDir, NULL, NULL );
|
|
vr->velocitytriggered = true;
|
|
}
|
|
}
|
|
}
|
|
//now try to get the right data
|
|
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tempAxis, G2Angles = {0, tempAngles[YAW], 0};
|
|
|
|
if ( cent->gent->handRBolt != -1 )
|
|
{
|
|
//Get handRPoint
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handRBolt,
|
|
&boltMatrix, G2Angles, ent.origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.handRPoint );
|
|
if (!cg.renderingThirdPerson && !cent->gent->client->ps.saberInFlight)
|
|
{
|
|
vec3_t angles;
|
|
BG_CalculateVRSaberPosition(cent->gent->client->renderInfo.handRPoint, angles);
|
|
}
|
|
}
|
|
if ( cent->gent->handLBolt != -1 )
|
|
{
|
|
//always get handLPoint too...?
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handLBolt,
|
|
&boltMatrix, G2Angles, ent.origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.handLPoint );
|
|
if (!cg.renderingThirdPerson && !cent->gent->client->ps.saberInFlight)
|
|
{
|
|
vec3_t angles;
|
|
BG_CalculateVROffHandPosition(cent->gent->client->renderInfo.handLPoint, angles);
|
|
}
|
|
}
|
|
if ( cent->gent->footLBolt != -1 )
|
|
{
|
|
//get the feet
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footLBolt,
|
|
&boltMatrix, G2Angles, ent.origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.footLPoint );
|
|
}
|
|
|
|
if ( cent->gent->footRBolt != -1 )
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footRBolt,
|
|
&boltMatrix, G2Angles, ent.origin, cg.time,
|
|
cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.footRPoint );
|
|
}
|
|
|
|
//Handle saber
|
|
if ( cent->gent && cent->gent->client && ( cent->currentState.weapon == WP_SABER || cent->currentState.saberInFlight ) && cent->gent->client->NPC_class != CLASS_ATST )
|
|
{//FIXME: somehow saberactive is getting lost over the network
|
|
if ( cent->gent->client->ps.saberEventFlags&SEF_INWATER )
|
|
{
|
|
cent->gent->client->ps.saberActive = qfalse;
|
|
}
|
|
if ( /*!cent->currentState.saberActive ||*/ !cent->gent->client->ps.saberActive || cent->gent->client->ps.saberLength > cent->gent->client->ps.saberLengthMax )//hack around network lag for now
|
|
{//saber is off
|
|
if ( cent->gent->client->ps.saberLength > 0 )
|
|
{
|
|
if ( cent->gent->client->ps.stats[STAT_HEALTH] <= 0 )
|
|
{//dead, didn't actively turn it off
|
|
cent->gent->client->ps.saberLength -= cent->gent->client->ps.saberLengthMax/10 * cg.frametime/100;
|
|
}
|
|
else
|
|
{//actively turned it off, shrink faster
|
|
cent->gent->client->ps.saberLength -= cent->gent->client->ps.saberLengthMax/10 * cg.frametime/100;
|
|
}
|
|
}
|
|
}
|
|
else if ( cent->gent->client->ps.saberLength < cent->gent->client->ps.saberLengthMax )
|
|
{//saber is on
|
|
if ( !cent->gent->client->ps.saberLength )
|
|
{
|
|
qhandle_t saberOnSound;
|
|
|
|
if ( cent->gent->client->playerTeam == TEAM_PLAYER )
|
|
{
|
|
saberOnSound = cgi_S_RegisterSound( "sound/weapons/saber/saberon.wav" );
|
|
}
|
|
else
|
|
{
|
|
saberOnSound = cgi_S_RegisterSound( "sound/weapons/saber/enemy_saber_on.wav" );
|
|
}
|
|
if ( !cent->gent->client->ps.weaponTime )
|
|
{//make us play the turn on anim
|
|
cent->gent->client->ps.weaponstate = WEAPON_RAISING;
|
|
cent->gent->client->ps.weaponTime = 250;
|
|
}
|
|
if ( cent->currentState.saberInFlight )
|
|
{//play it on the saber
|
|
cgi_S_UpdateEntityPosition( cent->gent->client->ps.saberEntityNum, g_entities[cent->gent->client->ps.saberEntityNum].currentOrigin );
|
|
cgi_S_StartSound (NULL, cent->gent->client->ps.saberEntityNum, CHAN_AUTO, saberOnSound );
|
|
}
|
|
else
|
|
{
|
|
cgi_S_StartSound (NULL, cent->currentState.number, CHAN_AUTO, saberOnSound );
|
|
}
|
|
}
|
|
if ( cg.frametime > 0 )
|
|
{
|
|
cent->gent->client->ps.saberLength += cent->gent->client->ps.saberLengthMax/10 * cg.frametime/100;//= saberLengthMax;
|
|
}
|
|
if ( cent->gent->client->ps.saberLength > cent->gent->client->ps.saberLengthMax )
|
|
{
|
|
cent->gent->client->ps.saberLength = cent->gent->client->ps.saberLengthMax;
|
|
}
|
|
}
|
|
|
|
if ( cent->gent->client->ps.saberLength > 0 )
|
|
{
|
|
if ( !cent->currentState.saberInFlight )//&& cent->currentState.saberActive)
|
|
{//holding the saber in-hand
|
|
// CGhoul2Info *currentModel = ¢->gent->ghoul2[1];
|
|
// CGhoul2Info *nextModel = ¢->gent->ghoul2[1];
|
|
//FIXME: need a version of this that *doesn't* need the mFileName in the ghoul2
|
|
//FIXME: use an actual surfaceIndex?
|
|
if ( !gi.G2API_GetSurfaceRenderStatus( ¢->gent->ghoul2[cent->gent->playerModel], "r_hand" ) )//surf is still on
|
|
{
|
|
CG_AddSaberBlade( cent, cent, NULL, CG_getPlayer1stPersonSaber(cent) ? 0 : ent.renderfx, cent->gent->weaponModel, ent.origin, tempAngles);
|
|
}//else, the limb will draw the blade itself
|
|
}//in-flight saber draws it's own blade
|
|
}
|
|
else
|
|
{
|
|
if ( cent->gent->client->ps.saberLength < 0 )
|
|
{
|
|
cent->gent->client->ps.saberLength = 0;
|
|
}
|
|
//if ( cent->gent->client->ps.saberEventFlags&SEF_INWATER )
|
|
{
|
|
CG_CheckSaberInWater( cent, cent, cent->gent->weaponModel, ent.origin, tempAngles );
|
|
}
|
|
}
|
|
if ( cent->currentState.weapon == WP_SABER && (cent->gent->client->ps.saberLength > 0 || cent->currentState.saberInFlight) )
|
|
{
|
|
calcedMp = qtrue;
|
|
}
|
|
}
|
|
|
|
if ( cent->currentState.number != 0
|
|
|| cg.renderingThirdPerson
|
|
|| cg.snap->ps.stats[STAT_HEALTH] <= 0
|
|
|| ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )//First person saber
|
|
)
|
|
{//if NPC, third person, or dead, unless using saber
|
|
//Get eyePoint & eyeAngles
|
|
/*
|
|
if ( cg.snap->ps.viewEntity > 0
|
|
&& cg.snap->ps.viewEntity < ENTITYNUM_WORLD
|
|
&& cg.snap->ps.viewEntity == cent->currentState.clientNum )
|
|
{//player is in an entity camera view, ME
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.eyePoint );
|
|
VectorCopy( tempAngles, cent->gent->client->renderInfo.eyeAngles );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.headPoint );
|
|
}
|
|
else
|
|
*/if ( cent->gent->headBolt == -1 )
|
|
{//no headBolt
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.eyePoint );
|
|
VectorCopy( tempAngles, cent->gent->client->renderInfo.eyeAngles );
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.headPoint );
|
|
}
|
|
else
|
|
{
|
|
//FIXME: if head is missing, we should let the dismembered head set our eyePoint...
|
|
gi.G2API_GetBoltMatrix(cent->gent->ghoul2, cent->gent->playerModel, cent->gent->headBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, cent->gent->client->renderInfo.eyePoint);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Y, tempAxis);
|
|
vectoangles( tempAxis, cent->gent->client->renderInfo.eyeAngles );
|
|
//estimate where the neck would be...
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Z, tempAxis);//go down to find neck
|
|
VectorMA( cent->gent->client->renderInfo.eyePoint, 8, tempAxis, cent->gent->client->renderInfo.headPoint );
|
|
}
|
|
//Get torsoPoint & torsoAngles
|
|
if (cent->gent->chestBolt>=0)
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->chestBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.torsoPoint );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, tempAxis );
|
|
vectoangles( tempAxis, cent->gent->client->renderInfo.torsoAngles );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.torsoPoint);
|
|
VectorClear(cent->gent->client->renderInfo.torsoAngles);
|
|
}
|
|
//get crotchPoint
|
|
if (cent->gent->crotchBolt>=0)
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->crotchBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.crotchPoint );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.crotchPoint);
|
|
}
|
|
|
|
if( !calcedMp )
|
|
{
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST)
|
|
{//FIXME: different for the three different weapon positions
|
|
mdxaBone_t boltMatrix;
|
|
int bolt;
|
|
entityState_t *es;
|
|
|
|
es = ¢->currentState;
|
|
|
|
// figure out where the actual model muzzle is
|
|
if (es->weapon == WP_ATST_MAIN)
|
|
{
|
|
if ( !es->number )
|
|
{//player, just use left one, I guess
|
|
if ( cent->gent->alt_fire )
|
|
{
|
|
bolt = cent->gent->handRBolt;
|
|
}
|
|
else
|
|
{
|
|
bolt = cent->gent->handLBolt;
|
|
}
|
|
}
|
|
else if (cent->gent->count > 0)
|
|
{
|
|
cent->gent->count = 0;
|
|
bolt = cent->gent->handLBolt;
|
|
}
|
|
else
|
|
{
|
|
cent->gent->count = 1;
|
|
bolt = cent->gent->handRBolt;
|
|
}
|
|
}
|
|
else // ATST SIDE weapons
|
|
{
|
|
if ( cent->gent->alt_fire)
|
|
{
|
|
bolt = cent->gent->genericBolt2;
|
|
}
|
|
else
|
|
{
|
|
bolt = cent->gent->genericBolt1;
|
|
}
|
|
}
|
|
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
|
|
}
|
|
else if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_GALAKMECH )
|
|
{
|
|
int bolt = -1;
|
|
if ( cent->gent->lockCount )
|
|
{//using the big laser beam
|
|
bolt = cent->gent->handLBolt;
|
|
}
|
|
else//repeater
|
|
{
|
|
if ( cent->gent->alt_fire )
|
|
{//fire from the lower barrel (not that anyone will ever notice this, but...)
|
|
bolt = cent->gent->genericBolt3;
|
|
}
|
|
else
|
|
{
|
|
bolt = cent->gent->handRBolt;
|
|
}
|
|
}
|
|
|
|
if ( bolt == -1 )
|
|
{
|
|
VectorCopy( ent.origin, cent->gent->client->renderInfo.muzzlePoint );
|
|
AngleVectors( tempAngles, cent->gent->client->renderInfo.muzzleDir, NULL, NULL );
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
|
|
}
|
|
}
|
|
else if (( cent->gent->weaponModel != -1) &&
|
|
( cent->gent->ghoul2.size() > cent->gent->weaponModel ) &&
|
|
( cent->gent->ghoul2[cent->gent->weaponModel].mModelindex != -1))
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
// figure out where the actual model muzzle is
|
|
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->weaponModel, 0, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( cent->gent->client->renderInfo.eyePoint, cent->gent->client->renderInfo.muzzlePoint );
|
|
AngleVectors( cent->gent->client->renderInfo.eyeAngles, cent->gent->client->renderInfo.muzzleDir, NULL, NULL );
|
|
}
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
}
|
|
|
|
// Pick the right effect for the type of weapon we are, defaults to no effect unless explicitly specified
|
|
if ( cent->muzzleFlashTime > 0 && wData && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
|
|
{
|
|
const char *effect = NULL;
|
|
|
|
cent->muzzleFlashTime = 0;
|
|
|
|
// Try and get a default muzzle so we have one to fall back on
|
|
if ( wData->mMuzzleEffect[0] )
|
|
{
|
|
effect = &wData->mMuzzleEffect[0];
|
|
}
|
|
|
|
if ( cent->altFire )
|
|
{
|
|
// We're alt-firing, so see if we need to override with a custom alt-fire effect
|
|
if ( wData->mAltMuzzleEffect[0] )
|
|
{
|
|
effect = &wData->mAltMuzzleEffect[0];
|
|
}
|
|
}
|
|
|
|
if (/*( cent->currentState.eFlags & EF_FIRING || cent->currentState.eFlags & EF_ALT_FIRING ) &&*/ effect )
|
|
{
|
|
if ( cent->gent && cent->gent->NPC )
|
|
{
|
|
theFxScheduler.PlayEffect( effect, cent->gent->client->renderInfo.muzzlePoint,
|
|
cent->gent->client->renderInfo.muzzleDir );
|
|
}
|
|
else
|
|
{
|
|
// We got an effect and we're firing, so let 'er rip.
|
|
theFxScheduler.PlayEffect( effect, cent->currentState.clientNum );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//play special force effects
|
|
if ( cent->gent->NPC && ( cent->gent->NPC->confusionTime > cg.time || cent->gent->NPC->charmedTime > cg.time || cent->gent->NPC->controlledTime > cg.time) )
|
|
{// we are currently confused, so play an effect at the headBolt position
|
|
theFxScheduler.PlayEffect( cgs.effects.forceConfusion, cent->gent->client->renderInfo.eyePoint );
|
|
}
|
|
|
|
if ( cent->gent->client && cent->gent->forcePushTime > cg.time )
|
|
{//being pushed
|
|
CG_ForcePushBodyBlur( cent, ent.origin, tempAngles );
|
|
}
|
|
|
|
if ( cent->gent->client->ps.powerups[PW_FORCE_PUSH] > cg.time || (cent->gent->client->ps.forcePowersActive & (1<<FP_GRIP)) )
|
|
{//doing the pushing/gripping
|
|
CG_ForcePushBlur( cent->gent->client->renderInfo.handLPoint );
|
|
}
|
|
|
|
if ( cent->gent->client->ps.eFlags & EF_FORCE_GRIPPED )
|
|
{//being gripped
|
|
CG_ForcePushBlur( cent->gent->client->renderInfo.headPoint );
|
|
}
|
|
|
|
if ( cent->gent->client && cent->gent->client->ps.powerups[PW_SHOCKED] > cg.time )
|
|
{//being electrocuted
|
|
CG_ForceElectrocution( cent, ent.origin, tempAngles );
|
|
}
|
|
|
|
if ( cent->gent->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
|
|
{//doing the electrocuting
|
|
vec3_t tAng, fxDir;
|
|
if (cent->gent->client->ps.clientNum == 0)
|
|
{
|
|
vec3_t origin, angles;
|
|
BG_CalculateVROffHandPosition(origin, tAng);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( cent->lerpAngles, tAng );
|
|
}
|
|
|
|
/*
|
|
if ( cent->currentState.number )
|
|
{
|
|
VectorSet( tAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 );
|
|
}
|
|
else
|
|
{//FIXME: this dir looks right on players, but not on NPCs, NPCs pe angles are absolute, not relative?
|
|
VectorSet( tAng, tempAngles[0]+cent->pe.torso.pitchAngle, tempAngles[1]+cent->pe.torso.yawAngle, 0 );
|
|
}
|
|
*/
|
|
if ( cent->gent->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
|
|
{//arc
|
|
vec3_t fxAxis[3];
|
|
AnglesToAxis( tAng, fxAxis );
|
|
theFxScheduler.PlayEffect( cgs.effects.forceLightningWide, cent->gent->client->renderInfo.handLPoint, fxAxis );
|
|
}
|
|
else
|
|
{//line
|
|
AngleVectors( tAng, fxDir, NULL, NULL );
|
|
theFxScheduler.PlayEffect( cgs.effects.forceLightning, cent->gent->client->renderInfo.handLPoint, fxDir );
|
|
}
|
|
}
|
|
|
|
//As good a place as any, I suppose, to do this keyframed sound thing
|
|
CGG2_AnimSounds( cent );
|
|
//setup old system for gun to look at
|
|
//CG_RunLerpFrame( ci, ¢->pe.torso, cent->gent->client->ps.torsoAnim, cent->gent->client->renderInfo.torsoFpsMod, cent->gent->s.number );
|
|
if ( cent->gent && cent->gent->client && cent->gent->client->ps.weapon == WP_SABER )
|
|
{
|
|
if ( cg_timescale.value < 1.0f && (cent->gent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
|
|
{
|
|
int wait = floor( (float)FRAMETIME/2.0f );
|
|
//sanity check
|
|
if ( cent->gent->client->ps.saberDamageDebounceTime - cg.time > wait )
|
|
{//when you unpause the game with force speed on, the time gets *really* wiggy...
|
|
cent->gent->client->ps.saberDamageDebounceTime = cg.time + wait;
|
|
}
|
|
if ( cent->gent->client->ps.saberDamageDebounceTime <= cg.time )
|
|
{
|
|
extern void WP_SaberDamageTrace( gentity_t *ent );
|
|
extern void WP_SaberUpdateOldBladeData( gentity_t *ent );
|
|
WP_SaberDamageTrace( cent->gent );
|
|
WP_SaberUpdateOldBladeData( cent->gent );
|
|
cent->gent->client->ps.saberDamageDebounceTime = cg.time + floor((float)wait*cg_timescale.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
refEntity_t legs;
|
|
refEntity_t torso;
|
|
refEntity_t head;
|
|
refEntity_t gun;
|
|
refEntity_t flash;
|
|
refEntity_t flashlight;
|
|
int renderfx, i;
|
|
const weaponInfo_t *weapon;
|
|
|
|
|
|
/*
|
|
Ghoul2 Insert End
|
|
*/
|
|
|
|
memset( &legs, 0, sizeof(legs) );
|
|
memset( &torso, 0, sizeof(torso) );
|
|
memset( &head, 0, sizeof(head) );
|
|
memset( &gun, 0, sizeof(gun) );
|
|
memset( &flash, 0, sizeof(flash) );
|
|
memset( &flashlight, 0, sizeof(flashlight) );
|
|
|
|
// Weapon sounds may need to be stopped, so check now
|
|
CG_StopWeaponSounds( cent );
|
|
|
|
//FIXME: pass in the axis/angles offset between the tag_torso and the tag_head?
|
|
// get the rotation information
|
|
CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis );
|
|
if ( cent->gent && cent->gent->client )
|
|
{
|
|
cent->gent->client->ps.legsYaw = cent->lerpAngles[YAW];
|
|
}
|
|
|
|
// get the animation state (after rotation, to allow feet shuffle)
|
|
// NB: Also plays keyframed animSounds (Bob- hope you dont mind, I was here late and at about 5:30 Am i needed to do something to keep me awake and i figured you wouldn't mind- you might want to check it, though, to make sure I wasn't smoking crack and missed something important, it is pretty late and I'm getting pretty close to being up for 24 hours here, so i wouldn't doubt if I might have messed something up, but i tested it and it looked right.... noticed in old code base i was doing it wrong too, whic h explains why I was getting so many damn sounds all the time! I had to lower the probabilities because it seemed like i was getting way too many sounds, and that was the problem! Well, should be fixed now I think...)
|
|
CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp,
|
|
&torso.oldframe, &torso.frame, &torso.backlerp );
|
|
|
|
cent->gent->client->renderInfo.legsFrame = cent->pe.legs.frame;
|
|
cent->gent->client->renderInfo.torsoFrame = cent->pe.torso.frame;
|
|
|
|
// add powerups floating behind the player
|
|
CG_PlayerPowerups( cent );
|
|
|
|
// add the shadow
|
|
shadow = CG_PlayerShadow( cent, &shadowPlane );
|
|
|
|
// add a water splash if partially in and out of water
|
|
CG_PlayerSplash( cent );
|
|
|
|
// get the player model information
|
|
renderfx = 0;
|
|
if ( !cg.renderingThirdPerson || cg.zoomMode )
|
|
{
|
|
if ( cg.snap->ps.viewEntity <= 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD)
|
|
{//no viewentity
|
|
if ( cent->currentState.number == cg.snap->ps.clientNum )
|
|
{//I am the player
|
|
if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
|
|
{//not using saber or fists
|
|
renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
}
|
|
}
|
|
else if ( cent->currentState.number == cg.snap->ps.viewEntity )
|
|
{//I am the view entity
|
|
if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
|
|
{//not using saber or fists
|
|
renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (cg_shadows.integer == 2) || (cg_shadows.integer == 3 && shadow) )
|
|
{
|
|
renderfx |= RF_SHADOW_PLANE;
|
|
}
|
|
renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
|
|
if ( cent->gent->NPC && cent->gent->NPC->scriptFlags & SCF_MORELIGHT )
|
|
{
|
|
renderfx |= RF_MORELIGHT; //bigger than normal min light
|
|
}
|
|
|
|
if ( cent->gent && cent->gent->client )
|
|
{
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.headPoint );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.handRPoint );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.handLPoint );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.footRPoint );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.footLPoint );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.torsoPoint );
|
|
VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.torsoAngles );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.crotchPoint );
|
|
}
|
|
if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD && cg.snap->ps.viewEntity == cent->currentState.clientNum )
|
|
{//player is in an entity camera view, ME
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.eyePoint );
|
|
VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.eyeAngles );
|
|
VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.headPoint );
|
|
}
|
|
//
|
|
// add the legs
|
|
//
|
|
legs.hModel = ci->legsModel;
|
|
legs.customSkin = ci->legsSkin;
|
|
|
|
VectorCopy( cent->lerpOrigin, legs.origin );
|
|
|
|
//Scale applied to a refEnt will apply to any models attached to it...
|
|
//This seems to copy the scale to every piece attached, kinda cool, but doesn't
|
|
//allow the body to be scaled up without scaling a bolt on or whatnot...
|
|
//Only apply scale if it's not 100% scale...
|
|
if(cent->currentState.modelScale[0] != 0.0f)
|
|
{
|
|
VectorScale( legs.axis[0], cent->currentState.modelScale[0], legs.axis[0] );
|
|
legs.nonNormalizedAxes = qtrue;
|
|
}
|
|
|
|
if(cent->currentState.modelScale[1] != 0.0f)
|
|
{
|
|
VectorScale( legs.axis[1], cent->currentState.modelScale[1], legs.axis[1] );
|
|
legs.nonNormalizedAxes = qtrue;
|
|
}
|
|
|
|
if(cent->currentState.modelScale[2] != 0.0f)
|
|
{
|
|
VectorScale( legs.axis[2], cent->currentState.modelScale[2], legs.axis[2] );
|
|
legs.nonNormalizedAxes = qtrue;
|
|
if ( !staticScale )
|
|
{
|
|
//FIXME:? need to know actual height of leg model bottom to origin, not hardcoded
|
|
legs.origin[2] += 24 * (cent->currentState.modelScale[2] - 1);
|
|
}
|
|
}
|
|
|
|
VectorCopy( legs.origin, legs.lightingOrigin );
|
|
legs.shadowPlane = shadowPlane;
|
|
legs.renderfx = renderfx;
|
|
VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all
|
|
|
|
CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, cent );
|
|
|
|
// if the model failed, allow the default nullmodel to be displayed
|
|
if (!legs.hModel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// add the torso
|
|
//
|
|
torso.hModel = ci->torsoModel;
|
|
if (torso.hModel)
|
|
{
|
|
orientation_t tag_torso;
|
|
|
|
torso.customSkin = ci->torsoSkin;
|
|
|
|
VectorCopy( cent->lerpOrigin, torso.lightingOrigin );
|
|
|
|
CG_PositionRotatedEntityOnTag( &torso, &legs, legs.hModel, "tag_torso", &tag_torso );
|
|
VectorCopy( torso.origin, cent->gent->client->renderInfo.torsoPoint );
|
|
vectoangles( tag_torso.axis[0], cent->gent->client->renderInfo.torsoAngles );
|
|
|
|
torso.shadowPlane = shadowPlane;
|
|
torso.renderfx = renderfx;
|
|
|
|
CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, cent );
|
|
|
|
//
|
|
// add the head
|
|
//
|
|
head.hModel = ci->headModel;
|
|
if (head.hModel)
|
|
{
|
|
orientation_t tag_head;
|
|
|
|
//Deal with facial expressions
|
|
CG_PlayerHeadExtension( cent, &head );
|
|
|
|
VectorCopy( cent->lerpOrigin, head.lightingOrigin );
|
|
|
|
CG_PositionRotatedEntityOnTag( &head, &torso, torso.hModel, "tag_head", &tag_head );
|
|
VectorCopy( head.origin, cent->gent->client->renderInfo.headPoint );
|
|
vectoangles( tag_head.axis[0], cent->gent->client->renderInfo.headAngles );
|
|
|
|
head.shadowPlane = shadowPlane;
|
|
head.renderfx = renderfx;
|
|
|
|
CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, cent );
|
|
|
|
if ( cent->gent && cent->gent->NPC && ( cent->gent->NPC->confusionTime > cg.time || cent->gent->NPC->charmedTime > cg.time || cent->gent->NPC->controlledTime > cg.time) )
|
|
{
|
|
// we are currently confused, so play an effect
|
|
theFxScheduler.PlayEffect( cgs.effects.forceConfusion, head.origin );
|
|
}
|
|
|
|
if ( !calcedMp )
|
|
{//First person player's eyePoint and eyeAngles should be copies from cg.refdef...
|
|
//Calc this client's eyepoint
|
|
VectorCopy( head.origin, cent->gent->client->renderInfo.eyePoint );
|
|
// race is gone, eyepoint should refer to the tag/bolt on the model... if this breaks something let me know - dmv
|
|
// VectorMA( cent->gent->client->renderInfo.eyePoint, CG_EyePointOfsForRace[cent->gent->client->race][1]*scaleFactor[2], head.axis[2], cent->gent->client->renderInfo.eyePoint );//up
|
|
// VectorMA( cent->gent->client->renderInfo.eyePoint, CG_EyePointOfsForRace[cent->gent->client->race][0]*scaleFactor[0], head.axis[0], cent->gent->client->renderInfo.eyePoint );//forward
|
|
//Calc this client's eyeAngles
|
|
vectoangles( head.axis[0], cent->gent->client->renderInfo.eyeAngles );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( torso.origin, cent->gent->client->renderInfo.eyePoint );
|
|
cent->gent->client->renderInfo.eyePoint[2] += cent->gent->maxs[2] - 4;
|
|
vectoangles( torso.axis[0], cent->gent->client->renderInfo.eyeAngles );
|
|
}
|
|
|
|
//
|
|
// add the gun
|
|
//
|
|
CG_RegisterWeapon( cent->currentState.weapon );
|
|
weapon = &cg_weapons[cent->currentState.weapon];
|
|
|
|
gun.hModel = weapon->weaponWorldModel;
|
|
if (gun.hModel)
|
|
{
|
|
qboolean drawGun = qtrue;
|
|
//FIXME: allow scale, animation and angle offsets
|
|
VectorCopy( cent->lerpOrigin, gun.lightingOrigin );
|
|
|
|
//FIXME: allow it to be put anywhere and move this out of if(torso.hModel)
|
|
//Will have to call CG_PositionRotatedEntityOnTag
|
|
|
|
//CG_PositionEntityOnTag( &gun, &torso, torso.hModel, "tag_weapon");
|
|
|
|
vec3_t angs;
|
|
BG_CalculateVRWeaponPosition(gun.origin, angs);
|
|
AnglesToAxis(angs, gun.axis);
|
|
//Gotta move this forward but test for now
|
|
VectorCopy( gun.origin, gun.lightingOrigin );
|
|
|
|
|
|
//--------------------- start saber hacks
|
|
|
|
if ( cent->gent && cent->gent->client && ( cent->currentState.weapon == WP_SABER || cent->currentState.saberInFlight ) )
|
|
{
|
|
if ( !cent->gent->client->ps.saberActive )//!cent->currentState.saberActive )
|
|
{//saber is off
|
|
if ( cent->gent->client->ps.saberLength > 0 )
|
|
{
|
|
if ( cent->gent->client->ps.stats[STAT_HEALTH] <= 0 )
|
|
{//dead, didn't actively turn it off
|
|
cent->gent->client->ps.saberLength -= cent->gent->client->ps.saberLengthMax/10 * cg.frametime/100;
|
|
}
|
|
else
|
|
{//actively turned it off, shrink faster
|
|
cent->gent->client->ps.saberLength -= cent->gent->client->ps.saberLengthMax/3 * cg.frametime/100;
|
|
}
|
|
}
|
|
if ( cent->gent->client->ps.saberLength < 0 )
|
|
{
|
|
cent->gent->client->ps.saberLength = 0;
|
|
}
|
|
}
|
|
else if ( cent->gent->client->ps.saberLength < cent->gent->client->ps.saberLengthMax )
|
|
{//saber is on
|
|
if ( !cent->gent->client->ps.saberLength )
|
|
{
|
|
if ( cent->currentState.saberInFlight )
|
|
{//play it on the saber
|
|
cgi_S_UpdateEntityPosition( cent->gent->client->ps.saberEntityNum, g_entities[cent->gent->client->ps.saberEntityNum].currentOrigin );
|
|
cgi_S_StartSound (NULL, cent->gent->client->ps.saberEntityNum, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/saber/saberon.wav" ) );
|
|
}
|
|
else
|
|
{
|
|
cgi_S_StartSound (NULL, cent->currentState.number, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/saber/saberon.wav" ) );
|
|
}
|
|
}
|
|
cent->gent->client->ps.saberLength += cent->gent->client->ps.saberLengthMax/6 * cg.frametime/100;//= saberLengthMax;
|
|
if ( cent->gent->client->ps.saberLength > cent->gent->client->ps.saberLengthMax )
|
|
{
|
|
cent->gent->client->ps.saberLength = cent->gent->client->ps.saberLengthMax;
|
|
}
|
|
}
|
|
|
|
if ( cent->currentState.saberInFlight )
|
|
{//not holding the saber in-hand
|
|
drawGun = qfalse;
|
|
}
|
|
if ( cent->gent->client->ps.saberLength > 0 )
|
|
{
|
|
if ( !cent->currentState.saberInFlight )
|
|
{//holding the saber in-hand
|
|
CG_AddSaberBlade(cent, cent, &gun, CG_getPlayer1stPersonSaber(cent) ? 0 : renderfx, 0, NULL, NULL );
|
|
calcedMp = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if ( cent->gent->client->ps.saberEventFlags&SEF_INWATER )
|
|
{
|
|
CG_CheckSaberInWater( cent, cent, 0, NULL, NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------- end saber hacks
|
|
|
|
gun.shadowPlane = shadowPlane;
|
|
gun.renderfx = renderfx;
|
|
|
|
if ( drawGun )
|
|
{
|
|
CG_AddRefEntityWithPowerups( &gun,
|
|
(cent->currentState.powerups & ((1<<PW_CLOAKED)|(1<<PW_BATTLESUIT)) ),
|
|
cent );
|
|
}
|
|
|
|
//
|
|
// add the flash (even if invisible)
|
|
//
|
|
|
|
// impulse flash
|
|
if ( cent->muzzleFlashTime > 0 && wData && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
|
|
{
|
|
int effect = 0;
|
|
|
|
cent->muzzleFlashTime = 0;
|
|
|
|
CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash");
|
|
|
|
// Try and get a default muzzle so we have one to fall back on
|
|
if ( wData->mMuzzleEffectID )
|
|
{
|
|
effect = wData->mMuzzleEffectID;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_ALT_FIRING )
|
|
{
|
|
// We're alt-firing, so see if we need to override with a custom alt-fire effect
|
|
if ( wData->mAltMuzzleEffectID )
|
|
{
|
|
effect = wData->mAltMuzzleEffectID;
|
|
}
|
|
}
|
|
|
|
if (( cent->currentState.eFlags & EF_FIRING || cent->currentState.eFlags & EF_ALT_FIRING ) && effect )
|
|
{
|
|
vec3_t up={0,0,1}, ax[3];
|
|
|
|
VectorCopy( flash.axis[0], ax[0] );
|
|
|
|
CrossProduct( up, ax[0], ax[1] );
|
|
CrossProduct( ax[0], ax[1], ax[2] );
|
|
|
|
if (( cent->gent && cent->gent->NPC ) || cg.renderingThirdPerson )
|
|
{
|
|
theFxScheduler.PlayEffect( effect, flash.origin, ax );
|
|
}
|
|
else
|
|
{
|
|
// We got an effect and we're firing, so let 'er rip.
|
|
theFxScheduler.PlayEffect( effect, flash.origin, ax );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !calcedMp && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
|
|
{// Set the muzzle point
|
|
orientation_t orientation;
|
|
|
|
cgi_R_LerpTag( &orientation, weapon->weaponModel, gun.oldframe, gun.frame,
|
|
1.0f - gun.backlerp, "tag_flash" );
|
|
|
|
// FIXME: allow origin offsets along tag?
|
|
VectorCopy( gun.origin, cent->gent->client->renderInfo.muzzlePoint );
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
VectorMA( cent->gent->client->renderInfo.muzzlePoint, orientation.origin[i], gun.axis[i], cent->gent->client->renderInfo.muzzlePoint );
|
|
}
|
|
// VectorCopy( gun.axis[0], cent->gent->client->renderInfo.muzzleDir );
|
|
// VectorAdd( gun.axis[0], orientation.axis[0], cent->gent->client->renderInfo.muzzleDir );
|
|
// VectorNormalize( cent->gent->client->renderInfo.muzzleDir );
|
|
|
|
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
// Weapon wasn't firing anymore, so ditch any weapon associated looping sounds.
|
|
//cent->gent->s.loopSound = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( legs.origin, cent->gent->client->renderInfo.eyePoint );
|
|
cent->gent->client->renderInfo.eyePoint[2] += cent->gent->maxs[2] - 4;
|
|
vectoangles( legs.axis[0], cent->gent->client->renderInfo.eyeAngles );
|
|
}
|
|
|
|
}
|
|
|
|
//FIXME: for debug, allow to draw a cone of the NPC's FOV...
|
|
if ( cent->currentState.number == 0 && cg.renderingThirdPerson )
|
|
{
|
|
playerState_t *ps = &cg.predicted_player_state;
|
|
|
|
if (( ps->weaponstate == WEAPON_CHARGING_ALT && ps->weapon == WP_BRYAR_PISTOL )
|
|
|| ( ps->weapon == WP_BOWCASTER && ps->weaponstate == WEAPON_CHARGING )
|
|
|| ( ps->weapon == WP_DEMP2 && ps->weaponstate == WEAPON_CHARGING_ALT ))
|
|
{
|
|
int shader = 0;
|
|
float val = 0.0f, scale = 1.0f;
|
|
vec3_t WHITE = {1.0f,1.0f,1.0f};
|
|
|
|
if ( ps->weapon == WP_BRYAR_PISTOL )
|
|
{
|
|
// Hardcoded max charge time of 1 second
|
|
val = ( cg.time - ps->weaponChargeTime ) * 0.001f;
|
|
shader = cgi_R_RegisterShader( "gfx/effects/bryarFrontFlash" );
|
|
}
|
|
else if ( ps->weapon == WP_BOWCASTER )
|
|
{
|
|
// Hardcoded max charge time of 1 second
|
|
val = ( cg.time - ps->weaponChargeTime ) * 0.001f;
|
|
shader = cgi_R_RegisterShader( "gfx/effects/greenFrontFlash" );
|
|
}
|
|
else if ( ps->weapon == WP_DEMP2 )
|
|
{
|
|
// Hardcoded max charge time of 1 second
|
|
val = ( cg.time - ps->weaponChargeTime ) * 0.001f;
|
|
shader = cgi_R_RegisterShader( "gfx/misc/lightningFlash" );
|
|
scale = 1.75f;
|
|
}
|
|
|
|
if ( val < 0.0f )
|
|
{
|
|
val = 0.0f;
|
|
}
|
|
else if ( val > 1.0f )
|
|
{
|
|
val = 1.0f;
|
|
CGCam_Shake( 0.1f, 100 );
|
|
}
|
|
else
|
|
{
|
|
CGCam_Shake( val * val * 0.3f, 100 );
|
|
}
|
|
|
|
val += Q_flrand(0.0f, 1.0f) * 0.5f;
|
|
|
|
FX_AddSprite( cent->gent->client->renderInfo.muzzlePoint, NULL, NULL, 3.0f * val * scale, 0.0f, 0.7f, 0.7f, WHITE, WHITE, Q_flrand(0.0f, 1.0f) * 360, 0.0f, 1.0f, shader, FX_USE_ALPHA );
|
|
}
|
|
}
|
|
}
|
|
|
|
//=====================================================================
|
|
|
|
/*
|
|
===============
|
|
CG_ResetPlayerEntity
|
|
|
|
A player just came into view or teleported, so reset all animation info
|
|
|
|
FIXME: We do not need to do this, we can remember the last anim and frame they were
|
|
on and coontinue from there.
|
|
===============
|
|
*/
|
|
void CG_ResetPlayerEntity( centity_t *cent ) {
|
|
cent->errorTime = -99999; // guarantee no error decay added
|
|
cent->extrapolated = qfalse;
|
|
|
|
if ( cent->gent && cent->gent->ghoul2.size() )
|
|
{
|
|
if ( cent->currentState.clientNum < MAX_CLIENTS )
|
|
{
|
|
CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim );
|
|
CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim );
|
|
}
|
|
else if ( cent->gent && cent->gent->client )
|
|
{
|
|
CG_ClearLerpFrame( ¢->gent->client->clientInfo, ¢->pe.legs, cent->currentState.legsAnim );
|
|
CG_ClearLerpFrame( ¢->gent->client->clientInfo, ¢->pe.torso, cent->currentState.torsoAnim );
|
|
}
|
|
}
|
|
//else????
|
|
|
|
EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
|
|
EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
|
|
|
|
VectorCopy( cent->lerpOrigin, cent->rawOrigin );
|
|
VectorCopy( cent->lerpAngles, cent->rawAngles );
|
|
|
|
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;
|
|
|
|
if ( cg_debugPosition.integer ) {
|
|
CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
|
|
}
|
|
}
|
|
|