3678 lines
106 KiB
C++
3678 lines
106 KiB
C++
|
|
#define CG_PLAYERS_CPP
|
|
#include "cg_local.h"
|
|
#include "fx_public.h"
|
|
#include "..\game\anims.h"
|
|
#include "..\game\boltOns.h"
|
|
|
|
#define LOOK_SWING_SCALE 0.5
|
|
|
|
/*
|
|
|
|
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( animsounds_t *animSounds, int frame, int entNum );
|
|
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 animFileSet_t knownAnimFileSets[MAX_ANIM_FILES];
|
|
|
|
//Basic set of custom sounds that everyone needs
|
|
const char *cg_customBasicSoundNames[MAX_CUSTOM_BASIC_SOUNDS] =
|
|
{
|
|
"*death1.wav",
|
|
"*death2.wav",
|
|
"*death3.wav",
|
|
"*jump1.wav",
|
|
"*pain25.wav",
|
|
"*pain50.wav",
|
|
"*pain75.wav",
|
|
"*pain100.wav",
|
|
"*gasp.wav",
|
|
"*fall1.wav",
|
|
};
|
|
|
|
//Used as a supplement to the basic set for enemies and hazard team
|
|
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",
|
|
"*combat1.wav", //Say when acquire a new enemy during combat
|
|
"*combat2.wav",
|
|
"*combat3.wav",
|
|
"*victory1.wav", //Say when killed an enemy
|
|
"*victory2.wav",
|
|
"*victory3.wav",
|
|
"*settle1.wav", //Say when killed an enemy anc can't find anymore (battle over)
|
|
"*settle2.wav",
|
|
"*settle3.wav",
|
|
};
|
|
|
|
//Used as a supplement to the basic set for crew and hazard team
|
|
const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] =
|
|
{
|
|
"*greeting1.wav", //See player in hall, greet them
|
|
"*greeting2.wav",
|
|
"*greeting3.wav",
|
|
"*response1.wav", //Used by player when just standing around, no enemy
|
|
"*response2.wav",
|
|
"*response3.wav",
|
|
"*busy1.wav", //After responding a couple times, start playing busy
|
|
"*busy2.wav",
|
|
"*busy3.wav",
|
|
"*mission1.wav", //Say when used by player while in formation and no enemy
|
|
"*mission2.wav",
|
|
"*mission3.wav",
|
|
"*blocked1.wav", //Say when killed an enemy anc can't find anymore (battle over)
|
|
"*blocked2.wav",
|
|
"*blocked3.wav",
|
|
"*ff_1a.wav", //Friendly fire, level 1, subset A
|
|
"*ff_1b.wav",
|
|
"*ff_1c.wav",
|
|
"*ff_2a.wav", //Friendly fire, level 2, subset A
|
|
"*ff_2b.wav",
|
|
"*ff_2c.wav",
|
|
"*ff_3a.wav", //Friendly fire, level 3, subset A
|
|
"*ff_3b.wav",
|
|
"*ff_3c.wav",
|
|
};
|
|
|
|
//Scavenger combat sounds table
|
|
const char *cg_customScavSoundNames[MAX_CUSTOM_SCAV_SOUNDS] =
|
|
{
|
|
"*coverme.wav", //"Cover me!"
|
|
"*getdown.wav", //"Get down!"
|
|
"*mr_get.wav", //"Get him!"
|
|
"*mr_got.wav", //"I got him!"
|
|
"*mr_kill.wav", //"Kill him!"
|
|
"*mr_mine.wav", //"He's mine!"
|
|
"*mr_surround.wav", //"Surround him!"
|
|
"*overthere.wav", //"Over there!"
|
|
"*stop.wav", //"Stop the intruder!"
|
|
"*takecover.wav", //"Take cover!"
|
|
"*whatisthat.wav", //"What's that?!"
|
|
"*watchit.wav", //"Watch it!"
|
|
"*illgo.wav", //"I'll go check it out..."
|
|
"*mr_go.wav", //"Where did he go?!"
|
|
};
|
|
/*
|
|
================
|
|
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 munro's sounds
|
|
ci = &g_entities[0].client->clientInfo;
|
|
}
|
|
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_SCAV:
|
|
// There should always be a clientInfo structure if there is a client, but just make sure...
|
|
if ( ci )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_SCAV_SOUNDS && cg_customScavSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customScavSoundNames[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_SCAV_SOUNDS && cg_customScavSoundNames[i] ; i++ )
|
|
{
|
|
if ( !Q_stricmp( soundName, cg_customScavSoundNames[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), qtrue);
|
|
|
|
// torsoModel
|
|
v = Info_ValueForKey( configstring, "torsoModel" );
|
|
|
|
Q_strncpyz( g_entities[clientNum].client->renderInfo.torsoModelName, v,
|
|
sizeof( g_entities[clientNum].client->renderInfo.torsoModelName), qtrue);
|
|
|
|
// headModel
|
|
v = Info_ValueForKey( configstring, "headModel" );
|
|
|
|
Q_strncpyz( g_entities[clientNum].client->renderInfo.headModelName, v,
|
|
sizeof( g_entities[clientNum].client->renderInfo.headModelName), qtrue);
|
|
|
|
// sounds
|
|
cvar_t *sex = gi.cvar( "sex", "male", 0 );
|
|
if ( Q_stricmp("female", sex->string ) == 0 )
|
|
{
|
|
ci->customBasicSoundDir = "alexa";
|
|
}
|
|
else
|
|
{
|
|
ci->customBasicSoundDir = "munro";
|
|
}
|
|
|
|
//player uses only the basic custom sound set, not the combat or extra
|
|
for ( i = 0 ; i < MAX_CUSTOM_BASIC_SOUNDS ; i++ )
|
|
{
|
|
s = cg_customBasicSoundNames[i];
|
|
if ( !s )
|
|
{
|
|
break;
|
|
}
|
|
ci->sounds[i] = cgi_S_RegisterSound( va("sound/voice/%s/misc/%s", ci->customBasicSoundDir, s + 1) );
|
|
}
|
|
|
|
ci->infoValid = qfalse;
|
|
}
|
|
|
|
/*
|
|
CG_RegisterNPCCustomSounds
|
|
*/
|
|
extern sfxHandle_t CG_RegisterSexedSound(char* string);
|
|
void CG_RegisterNPCCustomSounds( clientInfo_t *ci )
|
|
{
|
|
const char *s;
|
|
int i;
|
|
|
|
// sounds
|
|
|
|
if ( ci->customBasicSoundDir && ci->customBasicSoundDir[0] )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_BASIC_SOUNDS ; i++ )
|
|
{
|
|
s = cg_customBasicSoundNames[i];
|
|
if ( !s )
|
|
{
|
|
break;
|
|
}
|
|
|
|
char finalName[MAX_QPATH];
|
|
|
|
Q_strncpyz(finalName, s+1, sizeof(finalName), qtrue);
|
|
ci->sounds[i] = CG_RegisterSexedSound( va("sound/voice/%s/misc/%s", ci->customBasicSoundDir, finalName) );
|
|
}
|
|
}
|
|
|
|
if ( ci->customCombatSoundDir && ci->customCombatSoundDir[0] )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_COMBAT_SOUNDS ; i++ )
|
|
{
|
|
s = cg_customCombatSoundNames[i];
|
|
if ( !s )
|
|
{
|
|
break;
|
|
}
|
|
|
|
char finalName[MAX_QPATH];
|
|
|
|
Q_strncpyz(finalName, s+1, sizeof(finalName), qtrue);
|
|
ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS] = CG_RegisterSexedSound( va("sound/voice/%s/misc/%s", ci->customCombatSoundDir, finalName) );
|
|
}
|
|
}
|
|
|
|
if ( ci->customExtraSoundDir && ci->customExtraSoundDir[0] )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_EXTRA_SOUNDS ; i++ )
|
|
{
|
|
s = cg_customExtraSoundNames[i];
|
|
if ( !s )
|
|
{
|
|
break;
|
|
}
|
|
|
|
char finalName[MAX_QPATH];
|
|
|
|
Q_strncpyz(finalName, s+1, sizeof(finalName), qtrue);
|
|
ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS] = CG_RegisterSexedSound( va("sound/voice/%s/misc/%s", ci->customExtraSoundDir, finalName) );
|
|
}
|
|
}
|
|
|
|
if ( ci->customScavSoundDir && ci->customScavSoundDir[0] )
|
|
{
|
|
for ( i = 0 ; i < MAX_CUSTOM_SCAV_SOUNDS ; i++ )
|
|
{
|
|
s = cg_customScavSoundNames[i];
|
|
if ( !s )
|
|
{
|
|
break;
|
|
}
|
|
|
|
char finalName[MAX_QPATH];
|
|
|
|
Q_strncpyz(finalName, s+1, sizeof(finalName), qtrue);
|
|
ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS] = CG_RegisterSexedSound( va("sound/voice/%s/misc/%s", ci->customScavSoundDir, finalName) );
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
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 )
|
|
{
|
|
int i;
|
|
|
|
switch( team )
|
|
{
|
|
case TEAM_BORG:
|
|
cgs.media.borgBeamInSound = cgi_S_RegisterSound("sound/enemies/borg/borgbeam.wav");
|
|
cgs.media.borgFlareShader = cgi_R_RegisterShader( "gfx/misc/borgflare" );
|
|
cgs.media.borgShieldShader = cgi_R_RegisterShader( "borgShield" );
|
|
cgs.media.borgRecycleSound = cgi_S_RegisterSound("sound/enemies/borg/borgrecycle.wav");
|
|
cgs.media.dnBoltShader = cgi_R_RegisterShader( "gfx/misc/dnBolt" );
|
|
cgs.media.spark1Sound = cgi_S_RegisterSound( "sound/ambience/spark1.wav" );
|
|
cgs.media.spark2Sound = cgi_S_RegisterSound( "sound/ambience/spark2.wav" );
|
|
cgs.media.spark3Sound = cgi_S_RegisterSound( "sound/ambience/spark3.wav" );
|
|
cgs.media.spark4Sound = cgi_S_RegisterSound( "sound/ambience/spark4.wav" );
|
|
cgs.media.spark5Sound = cgi_S_RegisterSound( "sound/ambience/spark5.wav" );
|
|
cgs.media.spark6Sound = cgi_S_RegisterSound( "sound/ambience/spark6.wav" );
|
|
break;
|
|
|
|
case TEAM_SCAVENGERS:
|
|
case TEAM_KLINGON:
|
|
case TEAM_MALON:
|
|
case TEAM_HIROGEN:
|
|
case TEAM_IMPERIAL:
|
|
cgs.media.scavBeamInSound = cgi_S_RegisterSound( "sound/enemies/scav/transout.wav" );
|
|
cgs.media.scavTransportShader = cgi_R_RegisterShader( "powerups/scavBeamEffect" );
|
|
//stab sounds for knife
|
|
cgi_S_RegisterSound( "sound/enemies/harvester/stab1.wav" );
|
|
cgi_S_RegisterSound( "sound/enemies/harvester/stab2.wav" );
|
|
cgi_S_RegisterSound( "sound/enemies/harvester/stab3.wav" );
|
|
break;
|
|
|
|
case TEAM_STASIS:
|
|
cgs.media.stasisBeamInSound = cgi_S_RegisterSound("sound/movers/stasistransporter.wav");
|
|
cgs.media.stasisAppearSound = cgi_S_RegisterSound("sound/enemies/etherians/appear.wav");
|
|
cgs.media.stasisBeamOutSound = cgi_S_RegisterSound("sound/ambience/stasis/intostasis.wav");
|
|
break;
|
|
|
|
case TEAM_FORGE:
|
|
cgs.media.borgFlareShader = cgi_R_RegisterShader( "gfx/misc/borgflare" );//For the reaver
|
|
cgs.media.forgeRemoveShader = cgi_R_RegisterShader( "gfx/misc/forge_fade" );
|
|
cgs.media.forgeRemovalSound = cgi_S_RegisterSound( "sound/enemies/remove.wav" );
|
|
cgs.media.forgeBeaminSound = cgi_S_RegisterSound( "sound/movers/armytransin.mp3" );
|
|
break;
|
|
|
|
case TEAM_PARASITE:
|
|
break;
|
|
|
|
case TEAM_8472:
|
|
cgs.media.speciesPortalShader = cgi_R_RegisterShader( "gfx/effects/species_portal" );
|
|
cgs.media.portalFlareShader = cgi_R_RegisterShader( "gfx/effects/portal_flare" );
|
|
cgs.media.speciesBeaminSound = cgi_S_RegisterSound( "sound/enemies/species8472/8472in.wav" );
|
|
cgs.media.speciesBeamoutSound = cgi_S_RegisterSound( "sound/enemies/species8472/8472out.wav" );
|
|
break;
|
|
|
|
case TEAM_BOTS:
|
|
// These chunks are actually used by every type of bot
|
|
cgs.media.warriorBotChunks[0] = cgi_R_RegisterModel( "models/chunks/warriorbot/head.md3" );
|
|
cgs.media.warriorBotChunks[1] = cgi_R_RegisterModel( "models/chunks/warriorbot/turret.md3" );
|
|
cgs.media.warriorBotChunks[2] = cgi_R_RegisterModel( "models/chunks/warriorbot/foot.md3" );
|
|
cgs.media.warriorBotChunks[3] = cgi_R_RegisterModel( "models/chunks/warriorbot/arm.md3" );
|
|
cgs.media.warriorBotChunks[4] = cgi_R_RegisterModel( "models/chunks/warriorbot/leg.md3" );
|
|
cgs.media.warbotExplodeSounds[0] = cgi_S_RegisterSound( "sound/enemies/warbot/explode1.wav" );
|
|
cgs.media.warbotExplodeSounds[1] = cgi_S_RegisterSound( "sound/enemies/warbot/explode2.wav" );
|
|
cgs.media.hunterSeekerHoverSound = cgi_S_RegisterSound( "sound/enemies/hunter/hover.wav" );
|
|
for ( i = 0; i < 3; i++ )
|
|
cgs.media.scoutbotExplodeSounds[i] = cgi_S_RegisterSound( va("sound/enemies/scoutbot/explode%i.mp3", i+1 ) );
|
|
|
|
break;
|
|
|
|
case TEAM_STARFLEET:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PLAYER ANIMATION
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
qboolean ValidAnimFileIndex ( int index )
|
|
{
|
|
if ( index < 0 || index >= MAX_ANIM_FILES )
|
|
{
|
|
Com_Printf( S_COLOR_RED "Bad animFileIndex: %d\n", index );
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
void ParseAnimationSndBlock(const char *filename, animsounds_t *animSounds, animation_t *animations, int *i,char **text_p)
|
|
{
|
|
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 )
|
|
{
|
|
// 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, 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;
|
|
}
|
|
strcpy(soundString, token);
|
|
|
|
//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] = cgi_S_RegisterSound( va( soundString, n ) );
|
|
}
|
|
animSounds->numRandomAnimSounds = num - 1;
|
|
}
|
|
else
|
|
{
|
|
animSounds->soundIndex[0] = cgi_S_RegisterSound( va( soundString ) );
|
|
#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, 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 < numKnownAnimFileSets; i++) {
|
|
knownAnimFileSets[i].soundsCached = qfalse;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_ParseAnimationSndFile
|
|
|
|
Read a configuration file containing animation sounds
|
|
models/players/munro/animsounds.cfg, etc
|
|
|
|
This file's presence is not required
|
|
|
|
======================
|
|
*/
|
|
void CG_ParseAnimationSndFile( const char *filename, int animFileIndex )
|
|
{
|
|
char *text_p;
|
|
int len;
|
|
char *token;
|
|
char text[20000];
|
|
char sfilename[MAX_QPATH];
|
|
fileHandle_t f;
|
|
int i, j, upper_i, lower_i;
|
|
animsounds_t *legsAnimSnds = knownAnimFileSets[animFileIndex].legsAnimSnds;
|
|
animsounds_t *torsoAnimSnds = knownAnimFileSets[animFileIndex].torsoAnimSnds;
|
|
animation_t *animations = knownAnimFileSets[animFileIndex].animations;
|
|
|
|
if ( 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
|
|
knownAnimFileSets[animFileIndex].soundsCached = qtrue;
|
|
|
|
// Load and parse animSounds.cfg file
|
|
Com_sprintf( sfilename, sizeof( sfilename ), "models/players/%s/animsounds.cfg", 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 >= sizeof( text ) - 1 )
|
|
{
|
|
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)
|
|
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( filename, torsoAnimSnds, animations, &upper_i, &text_p );
|
|
}
|
|
|
|
else if ( !Q_stricmp(token,"LOWERSOUNDS") ) // A batch of lower sounds
|
|
{
|
|
ParseAnimationSndBlock( filename, legsAnimSnds, animations, &lower_i, &text_p );
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
===============
|
|
CG_SetLerpFrameAnimation
|
|
|
|
may include ANIM_TOGGLEBIT
|
|
===============
|
|
*/
|
|
void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation )
|
|
{
|
|
animation_t *anim;
|
|
|
|
lf->animationNumber = newAnimation;
|
|
newAnimation &= ~ANIM_TOGGLEBIT;
|
|
|
|
if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS )
|
|
{
|
|
CG_Error( "Bad animation number: %i", newAnimation );
|
|
}
|
|
|
|
if ( !ValidAnimFileIndex( ci->animFileIndex ) )
|
|
{
|
|
CG_Error( "Bad animFileIndex: %i", ci->animFileIndex );
|
|
}
|
|
|
|
anim = &knownAnimFileSets[ci->animFileIndex].animations[ newAnimation ];
|
|
|
|
lf->animation = anim;
|
|
lf->animationTime = lf->frameTime + anim->initialLerp;
|
|
|
|
if ( cg_debugAnim.integer )
|
|
{
|
|
CG_Printf( "Anim: %i\n", newAnimation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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(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;
|
|
if ( cg_debugAnim.integer )
|
|
{
|
|
CG_Printf( "Clamp lf->frameTime\n");
|
|
}
|
|
}
|
|
|
|
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 clientNum;
|
|
int legsAnim;
|
|
int legsTurnAnim = -1;
|
|
qboolean newLegsFrame = qfalse;
|
|
qboolean newTorsoFrame = qfalse;
|
|
|
|
clientNum = cent->currentState.clientNum;
|
|
|
|
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 & ~ANIM_TOGGLEBIT);
|
|
|
|
// 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( knownAnimFileSets[ci->animFileIndex].legsAnimSnds, cent->pe.legs.frame, cent->currentState.number );
|
|
}
|
|
}
|
|
|
|
//HACKHACKHACK
|
|
/*
|
|
if ( cent->gent->client->playerTeam == TEAM_BORG &&
|
|
cent->gent->client->ps.weapon == WP_BORG_ASSIMILATOR )
|
|
{
|
|
if ( cent->gent->NPC->attackHoldTime > cg.time )
|
|
{
|
|
if ( cent->pe.torso.frame >= 468 && cent->pe.torso.frame < 478 )
|
|
{//Do not animate
|
|
*torsoOld = *torso = cent->pe.torso.frame = 468;
|
|
*torsoBackLerp = 0;
|
|
if ( ValidAnimFileIndex( ci->animFileIndex ) )
|
|
{
|
|
CG_PlayerAnimSounds(knownAnimFileSets[ci->animFileIndex].torsoAnimSnds, cent->pe.torso.frame, cent->currentState.number );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
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(knownAnimFileSets[ci->animFileIndex].torsoAnimSnds, cent->pe.torso.frame, cent->currentState.number );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
void CG_PlayerAnimSounds( animsounds_t *animSounds, 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
|
|
*/
|
|
void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, int entNum )
|
|
{
|
|
int i;
|
|
int holdSnd = -1;
|
|
qboolean playSound = qfalse;
|
|
|
|
if ( entNum == 0 && !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;
|
|
}
|
|
|
|
if (animSounds[i].keyFrame == frame)
|
|
{
|
|
// 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
|
|
{
|
|
cgi_S_StartSound( NULL, entNum, CHAN_AUTO, holdSnd );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
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 = AngleMod( *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 = AngleMod( *curAngle + move );
|
|
}
|
|
}
|
|
|
|
swing = AngleSubtract( *curAngle, normalAngle );
|
|
|
|
// clamp to no more than normalAngle + tolerance
|
|
if ( swing > clampMax )
|
|
{
|
|
*curAngle = AngleMod( normalAngle + clampMax );
|
|
}
|
|
else if ( swing < clampMin )
|
|
{
|
|
*curAngle = AngleMod( 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,
|
|
lockMode_t lockMode, qboolean *turning )
|
|
{
|
|
float swing;
|
|
float move;
|
|
float scale;
|
|
|
|
swing = AngleSubtract( destAngle, *curAngle );
|
|
|
|
if(swing == 0)
|
|
{//Don't have to turn
|
|
*turning = qfalse;
|
|
}
|
|
else if ( lockMode == LM_LOCKED )
|
|
{//Never turn
|
|
*turning = qfalse;
|
|
}
|
|
else if ( lockMode == LM_CLAMP )
|
|
{// see if beyond clamp tolerance
|
|
if ( swing > clampMax || swing < clampMin )
|
|
{//We're locked down, but trying to look somewhere beyond our range, so unlock
|
|
*turning = qtrue;
|
|
}
|
|
}
|
|
else if ( lockMode == LM_SWING )
|
|
{// see if a swing should be started
|
|
if ( swing > swingTolMax || swing < swingTolMin )
|
|
{
|
|
*turning = qtrue;
|
|
}
|
|
}
|
|
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 = AngleMod( *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 = AngleMod( *curAngle + move );
|
|
}
|
|
|
|
|
|
// clamp to no more than tolerance
|
|
if ( swing > clampMax )
|
|
{
|
|
*curAngle = AngleMod( destAngle - (clampMax - 1) );
|
|
}
|
|
else if ( swing < clampMin )
|
|
{
|
|
*curAngle = AngleMod( 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->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 )
|
|
{
|
|
centity_t *lookCent = &cg_entities[cent->gent->client->renderInfo.lookTarget];
|
|
if ( lookCent && lookCent->gent )
|
|
{
|
|
vec3_t lookDir, lookOrg, forward, eyeOrg;
|
|
float scaleFactor;
|
|
|
|
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_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
|
|
{
|
|
VectorCopy( lookCent->gent->currentOrigin, lookOrg );
|
|
}
|
|
//Look in dir of lookTarget
|
|
|
|
//NOTE: We have to calc the angle from the central axis of the head so the angle we get our of this is accurate for actual head model angles
|
|
scaleFactor = (float)(cent->gent->client->renderInfo.scaleXYZ[0])/100.0f;
|
|
AngleVectors( cent->gent->client->renderInfo.eyeAngles, forward, NULL, NULL );
|
|
VectorMA( cent->gent->client->renderInfo.eyePoint, CG_EyePointOfsForRace[cent->gent->client->race][0]*scaleFactor*-1, forward, 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
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_AddHeadBob
|
|
=================
|
|
*/
|
|
static qboolean CG_AddHeadBob( centity_t *cent )
|
|
{
|
|
renderInfo_t *renderInfo = ¢->gent->client->renderInfo;
|
|
int volume = gi.S_Override[cent->gent->s.clientNum];
|
|
int volChange = volume - renderInfo->lastVoiceVolume;
|
|
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 );
|
|
}
|
|
|
|
// We aren't back to our normal position yet, so we still have to apply headBobAngles
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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
|
|
|
|
===============
|
|
*/
|
|
void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] )
|
|
{
|
|
vec3_t legsAngles, torsoAngles, headAngles;
|
|
// float dest;
|
|
float speed;
|
|
vec3_t velocity;
|
|
static int movementOffsets[8] = { 0, 0, 45, 0, 0, 0, -45, 0 };
|
|
//static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
|
|
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.
|
|
VectorClear( viewAngles );
|
|
VectorCopy( cent->lerpAngles, lookAngles );
|
|
lookAngles[PITCH] = 0;
|
|
|
|
AnglesToAxis( lookAngles, legs );
|
|
AnglesToAxis( viewAngles, torso );
|
|
AnglesToAxis( viewAngles, head );
|
|
|
|
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
|
|
if(cent->gent->NPC->lookMode)
|
|
{//We have a specific kind of looking we are doing,
|
|
//lower the lookMode, slower the yawspeed
|
|
yawSpeed *= (float)cent->gent->NPC->lookMode/(float)LT_FULLFACE;
|
|
}
|
|
}
|
|
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] = AngleMod( headAngles[YAW] );
|
|
VectorClear( legsAngles );
|
|
VectorClear( torsoAngles );
|
|
|
|
// --------- yaw -------------
|
|
|
|
//Clamp and swing the legs
|
|
legsAngles[YAW] = headAngles[YAW] + movementOffsets[ (int)cent->currentState.angles2[YAW] ];//(-45 to 45)
|
|
|
|
if(cent->gent->client->renderInfo.renderFlags & RF_LOCKEDANGLE)
|
|
{
|
|
cent->pe.legs.yawAngle = cent->gent->client->renderInfo.lockYaw;
|
|
cent->pe.legs.yawing = qfalse;
|
|
}
|
|
else
|
|
{
|
|
CG_SwingAngles( legsAngles[YAW], legsYawSwingTolMin, legsYawSwingTolMax, torsoYawClampMin, torsoYawClampMax, maxYawSpeed, ¢->pe.legs.yawAngle, cent->gent->client->renderInfo.legsYawLockMode, ¢->pe.legs.yawing );
|
|
}
|
|
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
|
|
#if 0
|
|
swing = AngleSubtract( legsAngles[YAW], headAngles[YAW] );
|
|
scale = ( torsoYawClampMax ) ? ( fabs( swing ) / torsoYawClampMax ) : 1.0f;
|
|
|
|
torsoAngles[YAW] = legsAngles[YAW] - ( swing * scale );
|
|
#else
|
|
// 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, cent->gent->client->renderInfo.torsoYawLockMode, ¢->pe.torso.yawing);
|
|
torsoAngles[YAW] = cent->pe.torso.yawAngle;
|
|
#endif
|
|
|
|
// ---------- pitch -----------
|
|
|
|
//As the body twists to its extents, the back tends to arch backwards
|
|
|
|
#if 0
|
|
swing = AngleSubtract( legsAngles[PITCH], headAngles[PITCH] );
|
|
scale = ( torsoPitchClampMax ) ? ( fabs( swing ) / torsoPitchClampMax ) : 1.0f;
|
|
|
|
torsoAngles[PITCH] = legsAngles[PITCH] - ( swing * scale );
|
|
#else
|
|
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, cent->gent->client->renderInfo.torsoPitchLockMode, ¢->pe.torso.pitching );
|
|
torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
|
|
#endif
|
|
// --------- roll -------------
|
|
|
|
// lean towards the direction of travel
|
|
if ( cent->gent->s.eFlags & EF_BANK_STRAFE )
|
|
{
|
|
VectorCopy( cent->gent->client->ps.velocity, velocity );
|
|
speed = VectorNormalize( velocity );
|
|
|
|
if ( speed )
|
|
{
|
|
vec3_t axis[3];
|
|
float side;
|
|
|
|
// Magic number fun! Speed is used for banking, so modulate the speed by a sine wave
|
|
speed *= 0.4 * sin( (cg.time - cent->gent->fx_time + 200 ) * 0.003 );
|
|
|
|
// Clamp to prevent harsh rolling
|
|
if ( speed > 12 )
|
|
speed = 12;
|
|
|
|
AnglesToAxis( legsAngles, axis );
|
|
side = speed * DotProduct( velocity, axis[1] );
|
|
legsAngles[ROLL] -= side;
|
|
torsoAngles[ROLL] -= side;
|
|
|
|
|
|
side = speed * DotProduct( velocity, axis[0] );
|
|
legsAngles[PITCH] += side;
|
|
torsoAngles[PITCH] += side;
|
|
}
|
|
}
|
|
|
|
// pain twitch
|
|
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 );
|
|
}
|
|
|
|
//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] = AngleMod( 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
|
|
===============
|
|
*/
|
|
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 );
|
|
}
|
|
|
|
// flight plays a looped sound
|
|
if ( cent->currentState.powerups & ( 1 << PW_HIROGEN_SHIELD ) ) {
|
|
cgi_S_AddLoopingSound(cent->currentState.number, cent->lerpOrigin, vec3_origin,
|
|
cgi_S_RegisterSound( "sound/items/flight.wav" ) );
|
|
}
|
|
|
|
// powerball
|
|
if ( cent->currentState.powerups & ( 1 << PW_BALL ) ) {
|
|
CG_TrailItem( cent, cgs.media.ballModel );
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
qboolean CG_ApplyBoltOnToRefEnt (refEntity_t *newBoltOn, boltOn_t *boltOn, boltOnInfo_t *bOInfo, const vec3_t org, refEntity_t *targModel)
|
|
|
|
attaches boltOn refEnt to the proper model and tag
|
|
|
|
*/
|
|
qboolean CG_ApplyBoltOnToRefEnt (refEntity_t *newBoltOn, boltOn_t *boltOn, boltOnInfo_t *bOInfo, const vec3_t org, refEntity_t *targModel)
|
|
{
|
|
int i;
|
|
vec3_t dir;
|
|
|
|
memset( newBoltOn, 0, sizeof(*newBoltOn) );
|
|
|
|
newBoltOn->hModel = cgs.model_draw[boltOn->model.modelIndex];
|
|
if (!newBoltOn->hModel)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
newBoltOn->frame = bOInfo->frame;
|
|
|
|
VectorCopy( org, newBoltOn->lightingOrigin );
|
|
|
|
AnglesToAxis( boltOn->angleOffsets, newBoltOn->axis );
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
if ( boltOn->model.scaleXYZ[i] != 100.0f )
|
|
{
|
|
VectorScale( newBoltOn->axis[i], boltOn->model.scaleXYZ[i]/100.0f, newBoltOn->axis[i] );
|
|
newBoltOn->nonNormalizedAxes = qtrue;
|
|
}
|
|
}
|
|
|
|
//NB!!! targetTag must exist in targetModel
|
|
CG_PositionRotatedEntityOnTag( newBoltOn, targModel, targModel->hModel, boltOn->targetTag, NULL );
|
|
|
|
vectoangles( newBoltOn->axis[0], bOInfo->lastAngles );
|
|
//Add the origin offsets
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
if ( boltOn->originOffsets[i] )
|
|
{
|
|
VectorCopy( newBoltOn->axis[i], dir );
|
|
|
|
if ( newBoltOn->nonNormalizedAxes )
|
|
{//Need to normalize for this transform
|
|
VectorNormalize( dir );
|
|
}
|
|
|
|
VectorMA( newBoltOn->origin, boltOn->originOffsets[i], dir, newBoltOn->origin );
|
|
}
|
|
}
|
|
|
|
VectorCopy( newBoltOn->origin, bOInfo->lastOrigin );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_GetTeamFromUserinfo
|
|
|
|
Return team value based on client userinfo
|
|
===============
|
|
*/
|
|
int CG_GetTeamFromUserinfo( int clientNum ) {
|
|
const char *info, *teamString;
|
|
|
|
info = CG_ConfigString( CS_PLAYERS + clientNum );
|
|
teamString = Info_ValueForKey( info, "t" );
|
|
|
|
/*if ( !strcmp( teamString, "s" ) )
|
|
return TEAM_SPECTATOR;
|
|
|
|
if ( !strcmp( teamString, "r" ) )
|
|
return TEAM_RED;
|
|
|
|
if ( !strcmp( teamString, "b" ) )
|
|
return TEAM_BLUE;*/
|
|
|
|
return TEAM_FREE;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerFloatSprite
|
|
|
|
Float a sprite over the player's head
|
|
===============
|
|
*/
|
|
void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) {
|
|
int rf;
|
|
refEntity_t ent;
|
|
|
|
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) {
|
|
rf = RF_THIRD_PERSON; // only show in mirrors
|
|
} else {
|
|
rf = 0;
|
|
}
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
VectorCopy( cent->lerpOrigin, ent.origin );
|
|
ent.origin[2] += 48;
|
|
ent.reType = RT_SPRITE;
|
|
ent.customShader = shader;
|
|
ent.radius = 10;
|
|
ent.renderfx = rf;
|
|
cgi_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerShadow
|
|
|
|
Returns the Z component of the surface being shadowed
|
|
|
|
should it return a full plane instead of a Z?
|
|
===============
|
|
*/
|
|
#define SHADOW_DISTANCE 128
|
|
static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) {
|
|
vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2};
|
|
trace_t trace;
|
|
float alpha;
|
|
|
|
*shadowPlane = 0;
|
|
|
|
if ( cg_shadows.integer == 0 ) {
|
|
return qfalse;
|
|
}
|
|
|
|
// no shadows when invisible
|
|
if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) {
|
|
return qfalse;
|
|
}
|
|
|
|
// send a trace down from the player to the ground
|
|
VectorCopy( cent->lerpOrigin, end );
|
|
end[2] -= SHADOW_DISTANCE;
|
|
|
|
cgi_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
|
|
|
|
// no shadow if too high
|
|
if ( trace.fraction == 1.0 ) {
|
|
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,
|
|
cent->pe.legs.yawAngle, 1,1,1,alpha, qfalse, 16, qtrue );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerSplash
|
|
|
|
Draw a mark at the water surface
|
|
===============
|
|
*/
|
|
void CG_PlayerSplash( centity_t *cent ) {
|
|
vec3_t start, end;
|
|
trace_t trace;
|
|
int contents;
|
|
polyVert_t verts[4];
|
|
|
|
if ( !cg_shadows.integer ) {
|
|
return;
|
|
}
|
|
|
|
VectorCopy( cent->lerpOrigin, end );
|
|
end[2] -= 24;
|
|
|
|
// if the feet aren't in liquid, don't make a mark
|
|
// this won't handle moving water brushes, but they wouldn't draw right anyway...
|
|
contents = cgi_CM_PointContents( end, 0 );
|
|
if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) {
|
|
return;
|
|
}
|
|
|
|
VectorCopy( cent->lerpOrigin, start );
|
|
start[2] += 32;
|
|
|
|
// if the head isn't out of liquid, don't make a mark
|
|
contents = 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;
|
|
}
|
|
|
|
// create a mark polygon
|
|
VectorCopy( trace.endpos, verts[0].xyz );
|
|
verts[0].xyz[0] -= 32;
|
|
verts[0].xyz[1] -= 32;
|
|
verts[0].st[0] = 0;
|
|
verts[0].st[1] = 0;
|
|
verts[0].modulate[0] = 255;
|
|
verts[0].modulate[1] = 255;
|
|
verts[0].modulate[2] = 255;
|
|
verts[0].modulate[3] = 255;
|
|
|
|
VectorCopy( trace.endpos, verts[1].xyz );
|
|
verts[1].xyz[0] -= 32;
|
|
verts[1].xyz[1] += 32;
|
|
verts[1].st[0] = 0;
|
|
verts[1].st[1] = 1;
|
|
verts[1].modulate[0] = 255;
|
|
verts[1].modulate[1] = 255;
|
|
verts[1].modulate[2] = 255;
|
|
verts[1].modulate[3] = 255;
|
|
|
|
VectorCopy( trace.endpos, verts[2].xyz );
|
|
verts[2].xyz[0] += 32;
|
|
verts[2].xyz[1] += 32;
|
|
verts[2].st[0] = 1;
|
|
verts[2].st[1] = 1;
|
|
verts[2].modulate[0] = 255;
|
|
verts[2].modulate[1] = 255;
|
|
verts[2].modulate[2] = 255;
|
|
verts[2].modulate[3] = 255;
|
|
|
|
VectorCopy( trace.endpos, verts[3].xyz );
|
|
verts[3].xyz[0] += 32;
|
|
verts[3].xyz[1] -= 32;
|
|
verts[3].st[0] = 1;
|
|
verts[3].st[1] = 0;
|
|
verts[3].modulate[0] = 255;
|
|
verts[3].modulate[1] = 255;
|
|
verts[3].modulate[2] = 255;
|
|
verts[3].modulate[3] = 255;
|
|
|
|
cgi_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_LightningBolt
|
|
===============
|
|
*/
|
|
#define BEAM_VARIATION 25
|
|
|
|
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_DREADNOUGHT && !( cent->currentState.eFlags & EF_ALT_FIRING ))
|
|
|| cent->currentState.weapon == WP_PHASER
|
|
/*|| cent->currentState.weapon == WP_BOT_LASER*/ )
|
|
{ /*continue*/ }
|
|
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 || cg_thirdPerson.integer == 2 )
|
|
{
|
|
VectorCopy( origin, org );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( cg.refdef.vieworg, org );
|
|
}
|
|
|
|
// Find the impact point of the beam
|
|
VectorCopy( cent->lerpAngles, angs );
|
|
if ( cg_thirdPerson.integer == 2 )
|
|
{
|
|
angs[PITCH] = 0;
|
|
angs[ROLL] = 0;
|
|
}
|
|
AngleVectors( angs, forward, NULL, NULL );
|
|
|
|
VectorMA( org, weaponData[cent->currentState.weapon].range, forward, end );
|
|
|
|
// Add a subtle variation to the beam weapon's endpoint
|
|
for ( int i = 0; i < 3; i ++ )
|
|
{
|
|
end[i] += crandom() * BEAM_VARIATION;
|
|
}
|
|
|
|
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 + random() * 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_PHASER:
|
|
if ( cent->gent->client->ps.ammo[AMMO_PHASER] < 1 )
|
|
{
|
|
weak = qtrue;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_ALT_FIRING && !weak )
|
|
FX_PhaserAltFire( origin, trace.endpos, trace.plane.normal, spark, impact );
|
|
else
|
|
FX_PhaserFire( origin, trace.endpos, trace.plane.normal, spark, impact, weak );
|
|
break;
|
|
|
|
case WP_DREADNOUGHT:
|
|
vec3_t org;
|
|
|
|
// Move the beam back a bit to help cover up the poly edges on the fire beam
|
|
VectorMA( origin, -4, forward, org );
|
|
FX_DreadnoughtFire( org, trace.endpos, trace.plane.normal, spark );
|
|
break;
|
|
|
|
/*
|
|
case WP_BOT_LASER:
|
|
FX_BotLaser( origin, trace.endpos, trace.plane.normal, impact );
|
|
break;
|
|
*/
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_AddRefEntityWithPowerups
|
|
|
|
Adds a piece with modifications or duplications for powerups
|
|
===============
|
|
*/
|
|
void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, gentity_t *gent )
|
|
{
|
|
if ( !gent )
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
return;
|
|
}
|
|
|
|
// FIXME: What the heck!?! The power flags don't clear themselves?!?
|
|
if ( gent->client->ps.powerups[PW_QUAD] < cg.time )
|
|
{
|
|
gent->client->ps.powerups[PW_QUAD] = 0;
|
|
if ( (powerups & ( 1 << PW_QUAD )) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if ( gent->client->ps.powerups[PW_INVIS] < cg.time )
|
|
gent->client->ps.powerups[PW_INVIS] = 0;
|
|
|
|
if ( gent->client->ps.powerups[PW_DISINT_6] < cg.time )
|
|
gent->client->ps.powerups[PW_DISINT_6] = 0;
|
|
|
|
if ( gent->client->ps.powerups[PW_HASTE] < cg.time )
|
|
{//scav beam effect
|
|
gent->client->ps.powerups[PW_HASTE] = 0;
|
|
}
|
|
if ( gent->client->ps.powerups[PW_REGEN] < cg.time )
|
|
{//disruptor
|
|
gent->client->ps.powerups[PW_REGEN] = 0;
|
|
if ( (powerups & ( 1 << PW_REGEN )) )
|
|
{
|
|
//stop drawing him after this effect
|
|
gent->client->ps.eFlags |= EF_NODRAW;
|
|
return;
|
|
}
|
|
}
|
|
if ( gent->client->ps.powerups[PW_DISINT_1] < cg.time )
|
|
{//arc welder disintigration
|
|
gent->client->ps.powerups[PW_DISINT_1] = 0;
|
|
}
|
|
if ( gent->client->ps.powerups[PW_DISINT_2] < cg.time )
|
|
{//arc welder disintigration
|
|
gent->client->ps.powerups[PW_DISINT_2] = 0;
|
|
if ( (powerups & ( 1 << PW_DISINT_2 )) )
|
|
{
|
|
//stop drawing him after this effect
|
|
gent->client->ps.eFlags |= EF_NODRAW;
|
|
return;
|
|
}
|
|
}
|
|
if ( gent->client->ps.powerups[PW_DISINT_3] < cg.time )
|
|
{//phaser alt disruptor
|
|
gent->client->ps.powerups[PW_DISINT_3] = 0;
|
|
if ( (powerups & ( 1 << PW_DISINT_3 )) )
|
|
{
|
|
//stop drawing him after this effect
|
|
gent->client->ps.eFlags |= EF_NODRAW;
|
|
return;
|
|
}
|
|
}
|
|
if ( gent->client->ps.powerups[PW_BORG_SHIELD] < cg.time )
|
|
{
|
|
gent->client->ps.powerups[PW_BORG_SHIELD] = 0;
|
|
}
|
|
|
|
// Add in the base model if neither beaming state is set
|
|
if ( !gent->client->ps.powerups[PW_INVIS] &&
|
|
!gent->client->ps.powerups[PW_QUAD] &&
|
|
!gent->client->ps.powerups[PW_DISINT_6] &&
|
|
!gent->client->ps.powerups[PW_REGEN] &&
|
|
!gent->client->ps.powerups[PW_DISINT_2] &&
|
|
!gent->client->ps.powerups[PW_DISINT_3] )
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
// If beaming out, don't add the base model after we've gone past the halfway point
|
|
else if ( gent->client->ps.powerups[PW_QUAD] > cg.time + 2000 )
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
// If beaming in, don't add the base model until we reach the halfway point
|
|
else if ( gent->client->ps.powerups[PW_INVIS] > 0 && gent->client->ps.powerups[PW_INVIS] < cg.time + 2000 )
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
else if ( gent->client->ps.powerups[PW_DISINT_6] > 0 )
|
|
{
|
|
if ( (powerups & ( 1 << PW_DISINT_6 )) )
|
|
{// If beaming in (weapon), fade it in
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
else
|
|
{
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
}
|
|
/* else if ( gent->client->ps.powerups[PW_REGEN] > cg.time + 1000 )
|
|
{//FIXME: Alpha out over time
|
|
ent->shaderRGBA[3] = floor(float(gent->client->ps.powerups[PW_REGEN] - cg.time) / 2000.0f * 255.0f );
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
else if ( gent->client->ps.powerups[PW_DISINT_3] > cg.time + 1000 )
|
|
{//FIXME: Alpha out over time
|
|
ent->shaderRGBA[3] = floor(float(gent->client->ps.powerups[PW_DISINT_3] - cg.time) / 2000.0f * 255.0f );
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
*/
|
|
// We are beaming in or out, so it's ok to add in the beaming effect
|
|
if ( (powerups & ( 1 << PW_INVIS )) || (powerups & ( 1 << PW_QUAD )) )
|
|
{
|
|
ent->customShader = cgs.media.transportShader;
|
|
ent->shaderTime = gent->fx_time / 1000.0f;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
if ( (powerups & ( 1 << PW_DISINT_6 )) )
|
|
{
|
|
ent->customShader = cgs.media.transportShader;
|
|
ent->shaderTime = (gent->fx_time - 800.0f)/ 1000.0f;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
if ( (powerups & ( 1 << PW_HASTE )) )
|
|
{
|
|
ent->customShader = cgs.media.scavTransportShader;
|
|
ent->shaderTime = gent->fx_time / 1000.0f;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
// Compression rifle
|
|
if ( (powerups & ( 1 << PW_REGEN )) )
|
|
{
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255;
|
|
ent->shaderRGBA[3] = 0;
|
|
ent->customShader = cgs.media.disruptorShader;
|
|
ent->shaderTime = gent->fx_time / 1000.0f;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
// Phaser alt
|
|
if ( (powerups & ( 1 << PW_DISINT_3 )) )
|
|
{
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255;
|
|
ent->shaderRGBA[3] = 0;
|
|
ent->customShader = cgs.media.phaserDisruptorShader;
|
|
ent->shaderTime = gent->fx_time / 1000.0f;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
// Electricity
|
|
if ( (powerups & ( 1 << PW_DISINT_1 )) )
|
|
{
|
|
if ( (gent->client->ps.powerups[PW_DISINT_1] - cg.time ) > 1000 && random() > 0.4f )
|
|
{
|
|
// fade out over time
|
|
int brightness = floor(float(gent->client->ps.powerups[PW_DISINT_1] - cg.time - 4000.0f) / 4000.0f * 255.0f );
|
|
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = brightness;
|
|
ent->shaderRGBA[3] = 255;
|
|
ent->customShader = cgs.media.electricBodyShader;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
|
|
if ( random() > 0.9f )
|
|
cgi_S_StartSound ( ent->origin, gent->s.number, CHAN_AUTO, cgi_S_RegisterSound( weaponData[WP_DREADNOUGHT].altmissileHitSound ) );
|
|
}
|
|
}
|
|
|
|
// Quantum
|
|
if ( powerups & ( 1 << PW_DISINT_2 ) )
|
|
{
|
|
//expanding and disrupting
|
|
ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255;
|
|
ent->shaderRGBA[3] = 255;
|
|
|
|
ent->customShader = cgs.media.quantumDisruptorShader;
|
|
ent->shaderTime = gent->fx_time / 1000.0f;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
if ( powerups & ( 1 << PW_BORG_SHIELD ) )
|
|
{
|
|
ent->customShader = cgs.media.borgFullBodyShieldShader;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
if ( powerups & ( 1 << PW_HIROGEN_SHIELD ) )
|
|
{
|
|
ent->customShader = cgs.media.shieldShader;
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
|
|
if ( powerups & ( 1 << PW_FORGE_DEATH ) )
|
|
{
|
|
float percent;
|
|
|
|
percent = (cg.time - gent->client->ps.powerups[PW_FORGE_DEATH]) / 3000.0f;
|
|
|
|
if ( percent < 0 )
|
|
percent = 0;
|
|
|
|
if ( percent > 1 )
|
|
percent = 1;
|
|
|
|
ent->shaderRGBA[0] =
|
|
ent->shaderRGBA[1] =
|
|
ent->shaderRGBA[2] = percent * 255.0f;
|
|
ent->shaderRGBA[3] = 255;
|
|
|
|
ent->customShader = cgs.media.forgeRemoveShader;
|
|
|
|
cgi_R_AddRefEntityToScene( ent );
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
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.S_Override[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.S_Override[] 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 + gi.S_Override[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.S_Override[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 );
|
|
}
|
|
}
|
|
|
|
extern void FX_HunterSeekerThruster( vec3_t origin, vec3_t normal, qboolean dead );
|
|
extern void FX_BotTurretBolt( vec3_t end, vec3_t start, vec3_t normal );
|
|
|
|
static qboolean calcedMp = qfalse;
|
|
|
|
//--------------------------------------------------------------
|
|
void CG_HunterSeekerEffects( centity_t *cent, refEntity_t *model )
|
|
{
|
|
vec3_t org, axis[3];
|
|
vec3_t end;
|
|
trace_t tr;
|
|
|
|
// Alternately get one muzzle, then the other
|
|
if ( cent->gent->count )
|
|
CG_GetTagWorldPosition( model, "tag_weapon2", org, axis );
|
|
else
|
|
CG_GetTagWorldPosition( model, "tag_weapon3", org, axis );
|
|
|
|
// Save off the muzzlepoint so the game side can know roughly where to do the damage trace from
|
|
VectorCopy( org, cent->gent->client->renderInfo.muzzlePoint );
|
|
VectorCopy( axis[0], cent->gent->pos2 ); // stash forward vector
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time; // save off the time when the muzzle was last calc'ed
|
|
calcedMp = qtrue;
|
|
|
|
// Add in the effects cgame side, otherwise the effects will lag and not appear to come from the muzzle
|
|
if ( cent->gent->s.eFlags & EF_ALT_FIRING )
|
|
{
|
|
VectorMA( org, 1024, axis[0], end );
|
|
CG_Trace( &tr, org, NULL, NULL, end, cent->gent->s.number, MASK_SHOT );
|
|
|
|
VectorMA( tr.endpos, 0.5, axis[2], end );
|
|
VectorMA( org, 0.5, axis[2], org );
|
|
|
|
FX_BotTurretBolt( end, org, tr.plane.normal );
|
|
|
|
VectorMA( tr.endpos, -1, axis[2], end );
|
|
VectorMA( org, -1, axis[2], org );
|
|
|
|
FX_BotTurretBolt( end, org, tr.plane.normal );
|
|
|
|
cent->gent->s.eFlags &= ~EF_ALT_FIRING;
|
|
}
|
|
|
|
// Add the thruster effects
|
|
CG_GetTagWorldPosition( model, "tag_thruster1", org, axis );
|
|
FX_HunterSeekerThruster( org, axis[0], (cent->gent->s.eFlags & EF_DEAD) );
|
|
|
|
CG_GetTagWorldPosition( model, "tag_thruster2", org, axis );
|
|
FX_HunterSeekerThruster( org, axis[0], (cent->gent->s.eFlags & EF_DEAD) );
|
|
|
|
CG_GetTagWorldPosition( model, "tag_thruster3", org, axis );
|
|
FX_HunterSeekerThruster( org, axis[0], (cent->gent->s.eFlags & EF_DEAD) );
|
|
|
|
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.hunterSeekerHoverSound );
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
void CG_SlashTrailEffects( centity_t *cent, refEntity_t *model )
|
|
{
|
|
vec3_t org, org2, org3, org4;
|
|
qboolean two_handed = qfalse;
|
|
|
|
// Keep the effect going for the specified frames
|
|
// NOTE: if the animation frames change this will all fall apart again...sigh.
|
|
if ( (cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == BOTH_MELEE2
|
|
&& cent->pe.torso.frame >= 228 && cent->pe.torso.frame <= 230 )
|
|
{
|
|
CG_GetTagWorldPosition( model, "tag_rhand", org, NULL );
|
|
CG_GetTagWorldPosition( model, "tag_weapon2", org2, NULL );
|
|
}
|
|
// Keep the effect going for the specified frames
|
|
else if ( (cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) == BOTH_MELEE1
|
|
&& cent->pe.torso.frame >= 221 && cent->pe.torso.frame <= 223 )
|
|
{
|
|
two_handed = qtrue;
|
|
|
|
CG_GetTagWorldPosition( model, "tag_rhand", org, NULL );
|
|
CG_GetTagWorldPosition( model, "tag_weapon2", org2, NULL );
|
|
|
|
CG_GetTagWorldPosition( model, "tag_lhand", org3, NULL );
|
|
CG_GetTagWorldPosition( model, "tag_weapon", org4, NULL );
|
|
}
|
|
else
|
|
{
|
|
// This animation doesn't use slash trails, or it's just not time to start it yet
|
|
return;
|
|
}
|
|
|
|
// If there is a set of old points that we can use...then we'll use 'em!
|
|
if ( cent->gent->trigger_formation )
|
|
{
|
|
// Build a quadrilateral using the old set of points and the new points
|
|
FX_AddTri( org, org2, cent->gent->pos1, 0.1f, 0.0, 400, cgs.media.solidWhiteShader );
|
|
FX_AddTri( cent->gent->pos1, cent->gent->pos2, org2, 0.1f, 0.0, 400, cgs.media.solidWhiteShader );
|
|
|
|
if ( two_handed )
|
|
{
|
|
FX_AddTri( org3, org4, cent->gent->pos3, 0.1f, 0.0, 400, cgs.media.solidWhiteShader );
|
|
FX_AddTri( cent->gent->pos3, cent->gent->pos4, org4, 0.1f, 0.0, 400, cgs.media.solidWhiteShader );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Tell the system that we'll have a set of old points to use next frame
|
|
cent->gent->trigger_formation = qtrue;
|
|
}
|
|
|
|
// Save these puppies for later so that we can connect up the slash trail properly
|
|
VectorCopy( org, cent->gent->pos1 );
|
|
VectorCopy( org2, cent->gent->pos2 );
|
|
|
|
if ( two_handed )
|
|
{
|
|
VectorCopy( org3, cent->gent->pos3 );
|
|
VectorCopy( org4, cent->gent->pos4 );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
// CG_AddTagBasedEffects
|
|
//
|
|
// For reaver melee attacks, etc.
|
|
//--------------------------------------------------------------
|
|
void CG_AddTagBasedEffects( centity_t *cent, refEntity_t *legs, refEntity_t *torso )
|
|
{
|
|
if ( cent->gent->client->race == RACE_REAVER )
|
|
{
|
|
CG_SlashTrailEffects( cent, torso );
|
|
}
|
|
else if ( cent->gent->client->race == RACE_BOT )
|
|
{
|
|
// I hate sticmps!
|
|
if ( !stricmp( cent->gent->NPC_type, "hunterseeker" ) )
|
|
{
|
|
CG_HunterSeekerEffects( cent, legs );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This race doesn't have any tag based effects
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
CG_GetPlayerLightLevel
|
|
-------------------------
|
|
*/
|
|
|
|
void CG_GetPlayerLightLevel( centity_t *cent )
|
|
{
|
|
vec3_t ambient, directed, lightDir;
|
|
|
|
//Poll the renderer for the light level
|
|
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];
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
CG_ScaleEffects
|
|
|
|
Apply scaling effects
|
|
-------------------------
|
|
*/
|
|
|
|
#define STASIS_SCALE_DOWN_TIME 500.0f
|
|
#define STASIS_SCALE_UP_TIME 500.0f
|
|
|
|
void CG_ScaleEffects( centity_t *cent )
|
|
{
|
|
float scale = 0, max = 100;
|
|
|
|
if ( cent->gent->s.eFlags & EF_SCALE_UP &&
|
|
( cent->gent->client->race == RACE_STASIS
|
|
|| cent->gent->client->race == RACE_8472
|
|
|| cent->gent->client->playerTeam == TEAM_FORGE ) )
|
|
{
|
|
if ( cg.time > cent->gent->fx_time + STASIS_SCALE_UP_TIME )
|
|
{
|
|
// Kill the flag and then ensure that we are full size
|
|
cent->gent->s.eFlags &= ~EF_SCALE_UP;
|
|
VectorSet( cent->gent->client->renderInfo.scaleXYZ, max, max, max );
|
|
|
|
if ( cent->gent->client != NULL )
|
|
cent->gent->client->ps.eFlags &= ~EF_SCALE_UP;
|
|
}
|
|
else
|
|
{
|
|
// This assumes that full size is 100% along each axis
|
|
scale = (float)( cg.time - cent->gent->fx_time ) / STASIS_SCALE_UP_TIME;
|
|
scale *= max;
|
|
|
|
VectorSet( cent->gent->client->renderInfo.scaleXYZ, scale, scale, max );
|
|
}
|
|
}
|
|
|
|
if ( cent->gent->s.eFlags & EF_SCALE_DOWN &&
|
|
( cent->gent->client->race == RACE_STASIS
|
|
|| cent->gent->client->race == RACE_8472
|
|
|| cent->gent->client->playerTeam == TEAM_FORGE ) )
|
|
{
|
|
if ( cg.time > cent->gent->fx_time + STASIS_SCALE_DOWN_TIME )
|
|
{
|
|
// Kill the flag and then ensure that we are fully shrunk
|
|
cent->gent->s.eFlags &= ~EF_SCALE_DOWN;
|
|
VectorSet( cent->gent->client->renderInfo.scaleXYZ, 1, 1, 1 );
|
|
|
|
if ( cent->gent->client != NULL )
|
|
cent->gent->client->ps.eFlags &= ~EF_SCALE_DOWN;
|
|
}
|
|
else
|
|
{
|
|
// This assumes that full size is 100% along each axis, though
|
|
scale = (float)( cg.time - cent->gent->fx_time ) / STASIS_SCALE_DOWN_TIME;
|
|
scale = 100 - ( scale * 100 );
|
|
|
|
if ( scale > 100 )
|
|
scale = 100;
|
|
VectorSet( cent->gent->client->renderInfo.scaleXYZ, scale, scale, 100 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CG_AssimilationTubules( vec3_t start, vec3_t end, vec3_t up, float scale )
|
|
{
|
|
vec3_t control1, control2;//, control1_vel, control2_vel;
|
|
vec3_t direction;
|
|
float len, timecycle;
|
|
|
|
timecycle = (cg.time&1023)/1023.0f*(2*3.14);
|
|
VectorSubtract( end, start, direction );
|
|
len = VectorNormalize( direction );
|
|
|
|
VectorMA(start, len*0.20f-(sin(timecycle)*0.20f), up, control1 );
|
|
VectorMA(start, len*0.20f+(sin(timecycle)*0.20f), up, control2 );
|
|
|
|
/*
|
|
vectoangles( direction, control1_vel );
|
|
control1_vel[ROLL] += crandom() * 360;
|
|
AngleVectors( control1_vel, NULL, NULL, control1_vel );
|
|
|
|
vectoangles( direction, control2_vel );
|
|
control2_vel[ROLL] += crandom() * 360;
|
|
AngleVectors( control2_vel, NULL, NULL, control2_vel );
|
|
|
|
VectorScale( control1_vel, 10.0f + (100.0f * random()), control1_vel );
|
|
VectorScale( control2_vel, -10.0f + (-100.0f * random()), control2_vel );
|
|
*/
|
|
|
|
FX_AddBezier( start, end,
|
|
control1, control2, NULL, NULL, NULL, NULL,
|
|
scale, //scale
|
|
0.0f, //dscale
|
|
1.0f, //alpha
|
|
0.0f, //dalpha
|
|
1.0f, //killtime
|
|
cgs.media.assimTubesShader );
|
|
}
|
|
|
|
void CG_AddFaceDisruption( refEntity_t *ent, int powerups )
|
|
{
|
|
if ( powerups > cg.time )
|
|
{
|
|
vec3_t org, end;
|
|
float alpha = (powerups - cg.time)/2000.0f;//1.0 to 0.0
|
|
float lengthScale = 1.0 - alpha;//0.0 to 1.0
|
|
if ( alpha > 0.5 )
|
|
{
|
|
alpha = 1.0 - alpha;
|
|
}
|
|
|
|
//Right eye
|
|
VectorMA( ent->origin, 4, ent->axis[0], org );//forward
|
|
VectorMA( org, 1.5, ent->axis[1], org );//right
|
|
VectorMA( org, 4, ent->axis[2], org );//up
|
|
|
|
VectorMA( org, 4, ent->axis[2], end );//up
|
|
FX_AddLine( org, end, 1.0, 5, 0.0, 1.0, 0.0, 1.0, cgs.media.dkorangeParticleShader );
|
|
|
|
VectorMA( org, -4, ent->axis[0], org );//pull it back
|
|
VectorMA( org, 56*lengthScale, ent->axis[0], end );//forward
|
|
VectorMA( end, 6*lengthScale, ent->axis[1], end );//right
|
|
VectorMA( end, 6*lengthScale, ent->axis[2], end );//up
|
|
|
|
if( rand() & 1 )
|
|
{
|
|
FX_AddLine( org, end, 1.0, 2.0, 12.0, alpha, 0.0, 1.0, cgs.media.orangeParticleShader );
|
|
}
|
|
else
|
|
{
|
|
FX_AddLine( org, end, 1.0, 2.0, 12.0, alpha, 0.0, 1.0, cgs.media.yellowParticleShader );
|
|
}
|
|
|
|
//Left eye
|
|
VectorMA( ent->origin, 4, ent->axis[0], org );//forward
|
|
VectorMA( org, -1.5, ent->axis[1], org );//right
|
|
VectorMA( org, 4, ent->axis[2], org );//up
|
|
|
|
VectorMA( org, 4, ent->axis[2], end );//up
|
|
FX_AddLine( org, end, 1.0, 5, 0.0, 1.0, 0.0, 1.0, cgs.media.dkorangeParticleShader );
|
|
|
|
VectorMA( org, -4, ent->axis[0], org );//pull it back
|
|
VectorMA( org, 56*lengthScale, ent->axis[0], end );//forward
|
|
VectorMA( end, -6*lengthScale, ent->axis[1], end );//right
|
|
VectorMA( end, 6*lengthScale, ent->axis[2], end );//up
|
|
|
|
if( rand() & 1 )
|
|
{
|
|
FX_AddLine( org, end, 1.0, 2.0, 12.0, alpha, 0.0, 1.0, cgs.media.orangeParticleShader );
|
|
}
|
|
else
|
|
{
|
|
FX_AddLine( org, end, 1.0, 2.0, 12.0, alpha, 0.0, 1.0, cgs.media.yellowParticleShader );
|
|
}
|
|
|
|
//Mouth
|
|
VectorMA( ent->origin, 4, ent->axis[0], org );//forward
|
|
VectorMA( org, 1, ent->axis[2], org );//up
|
|
|
|
VectorMA( org, 4, ent->axis[2], end );//up
|
|
FX_AddLine( org, end, 1.0, 5, 0.0, 1.0, 0.0, 1.0, cgs.media.dkorangeParticleShader );
|
|
|
|
VectorMA( org, -4, ent->axis[0], org );//pull it back
|
|
VectorMA( org, 56*lengthScale, ent->axis[0], end );//forward
|
|
VectorMA( end, -2*lengthScale, ent->axis[2], end );//up
|
|
|
|
if( rand() & 1 )
|
|
{
|
|
FX_AddLine( org, end, 1.0, 2.0, 12.0, alpha, 0.0, 1.0, cgs.media.orangeParticleShader );
|
|
}
|
|
else
|
|
{
|
|
FX_AddLine( org, end, 1.0, 2.0, 12.0, alpha, 0.0, 1.0, cgs.media.yellowParticleShader );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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.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_PHASER] < 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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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?)
|
|
|
|
===============
|
|
*/
|
|
void CG_Player( centity_t *cent ) {
|
|
clientInfo_t *ci;
|
|
refEntity_t legs;
|
|
refEntity_t torso;
|
|
refEntity_t head;
|
|
refEntity_t gun;
|
|
refEntity_t flash;
|
|
refEntity_t flashlight;
|
|
int renderfx, i, index;
|
|
const weaponInfo_t *weapon;
|
|
const weaponData_t *wData;
|
|
qboolean shadow, staticScale = qfalse;
|
|
float shadowPlane, alpha;
|
|
vec3_t scaleFactor;
|
|
entityState_t *ent;
|
|
|
|
calcedMp = qfalse;
|
|
|
|
//Get the player's light level for stealth calculations
|
|
CG_GetPlayerLightLevel( cent );
|
|
|
|
if ( cent->currentState.eFlags & EF_NODRAW )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent = ¢->currentState;
|
|
|
|
//FIXME: make sure this thing has a gent and client
|
|
if(!cent->gent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!cent->gent->client)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((in_camera) && !(cent->currentState.eFlags & EF_NPC)) // If player in camera then no need for shadow
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(cent->currentState.number == 0 && !cg_thirdPerson.integer )
|
|
{
|
|
calcedMp = qtrue;
|
|
}
|
|
|
|
ci = ¢->gent->client->clientInfo;
|
|
|
|
if ( !ci->infoValid )
|
|
{
|
|
return;
|
|
}
|
|
|
|
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) );
|
|
|
|
CG_ScaleEffects( cent );
|
|
|
|
// Weapon sounds may need to be stopped, so check now
|
|
CG_StopWeaponSounds( cent );
|
|
|
|
//Set the scale
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
scaleFactor[i] = (float)(cent->gent->client->renderInfo.scaleXYZ[i])/100.0f;
|
|
}
|
|
|
|
//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 );
|
|
|
|
// 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, which 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 ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )
|
|
{
|
|
renderfx = RF_THIRD_PERSON; // only draw in mirrors
|
|
}
|
|
|
|
if ( cg_shadows.integer == 3 && shadow )
|
|
{
|
|
renderfx |= RF_SHADOW_PLANE;
|
|
}
|
|
renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
|
|
|
|
//
|
|
// 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... (except boltOns?)
|
|
//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(scaleFactor[0] != 1.0)
|
|
{
|
|
VectorScale( legs.axis[0], scaleFactor[0], legs.axis[0] );
|
|
legs.nonNormalizedAxes = qtrue;
|
|
}
|
|
|
|
if(scaleFactor[1] != 1.0)
|
|
{
|
|
VectorScale( legs.axis[1], scaleFactor[1], legs.axis[1] );
|
|
legs.nonNormalizedAxes = qtrue;
|
|
}
|
|
|
|
if(scaleFactor[2] != 1.0)
|
|
{
|
|
VectorScale( legs.axis[2], scaleFactor[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 * (scaleFactor[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&~(1<<PW_DISINT_6), cent->gent );
|
|
|
|
// 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&~(1<<PW_DISINT_6), cent->gent );
|
|
|
|
// Do slash trails, etc if necessary
|
|
CG_AddTagBasedEffects( cent, &legs, &torso );
|
|
|
|
// Borg assimilator wires
|
|
//If a Borg and weapon == assimilator and fire button down:
|
|
if ( cent->currentState.weapon == WP_BORG_ASSIMILATOR && cent->gent->client->buttons & BUTTON_ATTACK && cent->gent->enemy && cent->gent->enemy->client )//&& cent->gent->enemy->s.number != 0 )
|
|
{
|
|
vec3_t targetSpot, start, end, dir;
|
|
refEntity_t temp;
|
|
|
|
CG_PositionEntityOnTag( &temp, &torso, torso.hModel, "tag_lhand");
|
|
//find closer point: enemy tag_torso or enemy tag_head
|
|
if ( cent->gent->enemy->s.number == 0 )
|
|
{
|
|
VectorCopy( cg.refdef.vieworg, targetSpot );
|
|
targetSpot[2] -= 8;
|
|
}
|
|
else
|
|
{
|
|
float dist;
|
|
|
|
VectorSubtract( temp.origin, cent->gent->enemy->client->renderInfo.headPoint, dir );
|
|
dist = VectorLengthSquared( dir );
|
|
VectorSubtract( temp.origin, cent->gent->enemy->client->renderInfo.torsoPoint, dir );
|
|
if ( dist > VectorLengthSquared( dir ) )
|
|
{
|
|
VectorCopy( cent->gent->enemy->client->renderInfo.torsoPoint, targetSpot );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( cent->gent->enemy->client->renderInfo.headPoint, targetSpot );
|
|
}
|
|
}
|
|
|
|
//do a bezier curve of 2 assimilation tubules from tag_muzzle
|
|
//to the closer enemy tag
|
|
VectorSubtract( temp.origin, targetSpot, dir );
|
|
if ( VectorLengthSquared( dir ) <= 576 )//24 squared
|
|
{
|
|
VectorMA( temp.origin, 1, temp.axis[1], start );
|
|
VectorMA( targetSpot, 1, temp.axis[1], end );
|
|
CG_AssimilationTubules( start, end, temp.axis[2], 0.25f );
|
|
VectorMA( temp.origin, -1, temp.axis[1], start );
|
|
VectorMA( targetSpot, -1, temp.axis[1], end );
|
|
CG_AssimilationTubules( start, end, temp.axis[2], 0.25f );
|
|
}
|
|
}
|
|
//
|
|
// 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&~(1<<PW_DISINT_6), cent->gent );
|
|
CG_AddFaceDisruption( &head, cent->gent->client->ps.powerups[PW_REGEN] );
|
|
|
|
//Temp: Add special effects
|
|
if(cent->gent->client->race == RACE_BORG && (cent->currentState.eFlags&EF_EYEBEAM) && cent->gent->client->ps.stats[STAT_HEALTH] > 0)
|
|
{//Eyebeam, only if have an enemy? Or only if awake and walking around?
|
|
trace_t trace;
|
|
vec3_t beamOrg, beamEnd, rgb = {1.0f,0.0f,0.0f};
|
|
refEntity_t temp;
|
|
//FIXME: this should be attached to a eyepiece bolt-on eventually, not the head itself
|
|
|
|
CG_PositionEntityOnTag( &temp, &head, head.hModel, "tag_ear");
|
|
|
|
VectorCopy( temp.origin, beamOrg );
|
|
VectorMA( beamOrg, 1024, temp.axis[0], beamEnd);//forward to end
|
|
|
|
/*
|
|
VectorMA(head.origin, 6.35, head.axis[2], beamOrg);//up
|
|
VectorMA(beamOrg, 1.3, head.axis[1], beamOrg);//right
|
|
VectorMA(beamOrg, 5.65, head.axis[0], beamOrg);//forward
|
|
VectorMA(beamOrg, 1024, head.axis[0], beamEnd);//forward to end
|
|
*/
|
|
cgi_CM_BoxTrace( &trace, beamOrg, beamEnd, NULL, NULL, 0, MASK_SHOT );
|
|
VectorCopy(trace.endpos, beamEnd);
|
|
alpha = 1.0f - (random() * 0.5);
|
|
FX_AddLine( beamOrg, beamEnd, 1.0f, 0.35f + ( crandom() * 0.1 ), 0.0f, alpha, alpha, rgb,rgb,1.0f, cgs.media.whiteLaserShader );
|
|
VectorMA(beamOrg, 0.5, head.axis[0], beamOrg);//forward
|
|
FX_AddSprite( beamOrg, NULL, NULL, 1.0f + (random() * 2.0f), 0.0f, 0.9f, 0.9f, 0.0f, 0.0f, 0.0f, cgs.media.borgEyeFlareShader );
|
|
FX_AddQuad( beamEnd, trace.plane.normal, NULL, NULL, 2.0f + (crandom() * 1.0f), 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, cgs.media.borgEyeFlareShader );
|
|
}
|
|
|
|
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 );
|
|
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 );
|
|
}
|
|
|
|
//FIXME: this should really be a function call
|
|
//If have the Borg sparkies effect, apply it
|
|
if ( cent->gent->s.eFlags & EF_BORG_SPARKIES && Q_irand( 0, cg.frametime ) > 5 )
|
|
{
|
|
vec3_t angles, vec, spot;
|
|
|
|
VectorAdd( head.origin, torso.origin, spot );
|
|
VectorScale( spot, 0.5, spot );
|
|
spot[0] += crandom() * 4;
|
|
spot[1] += crandom() * 4;
|
|
spot[2] += crandom() * 4;
|
|
VectorSet( angles, Q_flrand(-360, 360), Q_flrand(-360, 360), Q_flrand(-360, 360) );
|
|
AngleVectors( angles, vec, 0, 0 );
|
|
if ( Q_irand( 0, 1) )
|
|
{
|
|
VectorScale( vec, Q_flrand( 300, 400 ), vec );
|
|
FX_BorgDeathSparkParticles( spot, angles, vec, NULL );
|
|
if ( !Q_irand( 0, 3 ) )
|
|
{
|
|
cgi_S_StartSound (NULL, cent->gent->s.number, CHAN_AUTO, cgi_S_RegisterSound ( va("sound/ambience/spark%d.wav", Q_irand(1, 6)) ) );
|
|
}
|
|
}
|
|
/*else
|
|
{
|
|
vec3_t end;
|
|
float duration = Q_irand( 50, 500 );
|
|
|
|
VectorMA( spot, Q_flrand( 16, 52 ), vec, end );
|
|
//NOTE: Maybe bezier curves from one tag to another?
|
|
FX_AddElectricity( spot, end,
|
|
1.0f, Q_flrand(3, 9), -45,
|
|
1.0f, 0.0f,
|
|
duration * 2, cgs.media.borgLightningShaders[Q_irand(0, 3)], FXF_WRAP | FXF_TAPER, 0.6f );
|
|
if ( !Q_irand( 0, 3 ) )
|
|
{
|
|
cgi_R_AddLightToScene( spot, Q_irand(100, 200), 100, 150, 50 );
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
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];
|
|
wData = &weaponData[cent->currentState.weapon];
|
|
|
|
gun.hModel = weapon->weaponWorldModel;
|
|
if (gun.hModel)
|
|
{
|
|
//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");
|
|
|
|
gun.shadowPlane = shadowPlane;
|
|
gun.renderfx = renderfx;
|
|
|
|
if ( cent->currentState.weapon != WP_BORG_TASER || Q_stricmp( "satan", cent->gent->NPC_type ) != 0 )
|
|
{//uber-hack: satan's robot doesn't actually draw the weapon in-hand
|
|
CG_AddRefEntityWithPowerups( &gun,
|
|
(cent->currentState.powerups & ( (1<<PW_INVIS)|(1<<PW_QUAD)|(1<<PW_BATTLESUIT)|(1<<PW_BORG_SHIELD)|(1<<PW_DISINT_6) ) ),
|
|
cent->gent );
|
|
}
|
|
|
|
//
|
|
// add the flash (even if invisible)
|
|
//
|
|
|
|
// impulse flash
|
|
if ( cg.time - cent->muzzleFlashTime <= MUZZLE_FLASH_TIME )
|
|
{
|
|
if ( cent->gent->alt_fire && weapon->flashaModel ) {
|
|
if (weapon->flasha2Model && ( rand() & 1 ) ) {
|
|
flash.hModel = weapon->flasha2Model;
|
|
} else {
|
|
flash.hModel = weapon->flashaModel;
|
|
}
|
|
}
|
|
else {
|
|
// always fallback to main flash
|
|
if (weapon->flash2Model && ( rand() & 1 ) ) {
|
|
flash.hModel = weapon->flash2Model;
|
|
} else {
|
|
flash.hModel = weapon->flashModel;
|
|
}
|
|
}
|
|
|
|
if (flash.hModel)
|
|
{
|
|
CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash");
|
|
flash.renderfx = renderfx | RF_NOSHADOW;
|
|
cgi_R_AddRefEntityToScene( &flash );
|
|
|
|
// make a dlight for the flash
|
|
if ( wData->flashColor[0] || wData->flashColor[1] || wData->flashColor[2] ) {
|
|
cgi_R_AddLightToScene( flash.origin, 200 + (rand()&31), wData->flashColor[0],
|
|
wData->flashColor[1], wData->flashColor[2] );
|
|
}
|
|
|
|
if(!calcedMp)
|
|
{
|
|
// add lightning bolt
|
|
CG_LightningBolt( cent, flash.origin );
|
|
|
|
VectorCopy( flash.origin, cent->gent->client->renderInfo.muzzlePoint );
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
calcedMp = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!calcedMp)
|
|
{// 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 );
|
|
}
|
|
|
|
cent->gent->client->renderInfo.mPCalcTime = cg.time;
|
|
// Weapon wasn't firing anymore, so ditch any weapon associated looping sounds.
|
|
//cent->gent->s.loopSound = 0;
|
|
}
|
|
/*
|
|
//Add a flashlight:
|
|
flashlight.hModel = cgs.media.flashLightModel;
|
|
if (flashlight.hModel)
|
|
{
|
|
//Add the model to trace the stencil:
|
|
CG_PositionEntityOnTag( &flashlight, &gun, weapon->weaponModel, "tag_flash");
|
|
flashlight.renderfx = renderfx;
|
|
|
|
AngleVectors(cent->lerpAngles, flashlight.lightDir, NULL, NULL);
|
|
VectorScale(flashlight.lightDir, -1, flashlight.lightDir);
|
|
|
|
cgi_R_AddRefEntityToScene( &flashlight );
|
|
|
|
//Add a lightcone model with lightcone shader
|
|
|
|
//Flag one of them to have a flare?
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
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 );
|
|
}
|
|
|
|
|
|
boltOnInfo_t *boltOnInfo;
|
|
//Bolt-Ons
|
|
for ( i = 0; i < MAX_BOLT_ONS; i++ )
|
|
{
|
|
boltOnInfo = ¢->gent->client->renderInfo.boltOns[i];
|
|
index = boltOnInfo->index;
|
|
|
|
if ( index >= 0 && index < numBoltOns )
|
|
{
|
|
boltOn_t *boltOn;
|
|
refEntity_t boltOnRefEnt;
|
|
qboolean attached = qfalse;
|
|
|
|
boltOn = &knownBoltOns[index];
|
|
switch( boltOn->targetModel )
|
|
{
|
|
case MODEL_HEAD:
|
|
if ( head.hModel )
|
|
{
|
|
attached = CG_ApplyBoltOnToRefEnt( &boltOnRefEnt, boltOn, ¢->gent->client->renderInfo.boltOns[i], cent->lerpOrigin, &head );
|
|
}
|
|
break;
|
|
case MODEL_TORSO:
|
|
if ( torso.hModel )
|
|
{
|
|
attached = CG_ApplyBoltOnToRefEnt( &boltOnRefEnt, boltOn, ¢->gent->client->renderInfo.boltOns[i], cent->lerpOrigin, &torso );
|
|
}
|
|
break;
|
|
case MODEL_LEGS:
|
|
if ( legs.hModel )
|
|
{
|
|
attached = CG_ApplyBoltOnToRefEnt( &boltOnRefEnt, boltOn, ¢->gent->client->renderInfo.boltOns[i], cent->lerpOrigin, &legs );
|
|
}
|
|
break;
|
|
case MODEL_WEAPON1:
|
|
if ( gun.hModel )
|
|
{
|
|
attached = CG_ApplyBoltOnToRefEnt( &boltOnRefEnt, boltOn, ¢->gent->client->renderInfo.boltOns[i], cent->lerpOrigin, &gun );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( attached )
|
|
{
|
|
boltOnRefEnt.shadowPlane = shadowPlane;
|
|
boltOnRefEnt.renderfx = renderfx;
|
|
//need to apply effects to boltOns too
|
|
CG_AddRefEntityWithPowerups( &boltOnRefEnt, cent->currentState.powerups&~(1<<PW_DISINT_6), cent->gent );
|
|
|
|
// It would have been better to use AddRefEntWithPowerups, except boltons don't have their own powerup flags
|
|
if ( boltOnInfo->fxFlags & BOLTON_BEAMIN )
|
|
{
|
|
boltOnRefEnt.customShader = cgs.media.transportShader;
|
|
boltOnRefEnt.shaderTime = (boltOnInfo->startTime + 2000 ) * 0.001f; // Scale startTime to server time
|
|
CG_AddRefEntityWithPowerups( &boltOnRefEnt, cent->currentState.powerups&~(1<<PW_DISINT_6), cent->gent );
|
|
|
|
if ( boltOnInfo->startTime + 1900 < cg.time )
|
|
{
|
|
boltOnInfo->fxFlags &= ~BOLTON_BEAMIN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//FIXME: for debug, allow to draw a cone of the NPC's FOV...
|
|
}
|
|
|
|
|
|
//=====================================================================
|
|
|
|
/*
|
|
===============
|
|
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->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 );
|
|
}
|
|
}
|
|
|