jedi-academy/codemp/cgame/cg_players.c
Jonathan Gray 832b4342a8 CVE-2006-2236 Buffer overflow in the Quake 3 Engine
CVE-2006-2236
Buffer overflow in the Quake 3 Engine, as used by (1) ET 2.60,
(2) Return to Castle Wolfenstein 1.41, and (3) Quake III Arena 1.32b
allows remote attackers to execute arbitrary commands via a long
remapShader command.

from Thilo Schulz in ioquake3
svn 765 git d21411452ef32b86c0b79ddcaf49221701dcdb07

Add string length checking to function COM_StripExtension. This fixes
the R_RemapShader buffer overflow exploit that can be found here:
http://milw0rm.com/exploits/1750
2013-05-07 22:18:45 +10:00

11277 lines
311 KiB
C

// Copyright (C) 1999-2000 Id Software, Inc.
//
// cg_players.c -- handle the media and animation for player entities
#include "cg_local.h"
#include "../ghoul2/G2.h"
#include "../game/bg_saga.h"
#define max(x,y) ((x)>(y)?(x):(y))
extern vmCvar_t cg_thirdPersonAlpha;
extern int cgSiegeTeam1PlShader;
extern int cgSiegeTeam2PlShader;
extern void CG_AddRadarEnt(centity_t *cent); //cg_ents.c
extern void CG_AddBracketedEnt(centity_t *cent); //cg_ents.c
extern qboolean CG_InFighter( void );
extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
//for g2 surface routines
#define TURN_ON 0x00000000
#define TURN_OFF 0x00000100
extern stringID_table_t animTable [MAX_ANIMATIONS+1];
char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = {
"*death1",
"*death2",
"*death3",
"*jump1",
"*pain25",
"*pain50",
"*pain75",
"*pain100",
"*falling1",
"*choke1",
"*choke2",
"*choke3",
"*gasp",
"*land1",
"*taunt",
NULL
};
//NPC sounds:
//Used as a supplement to the basic set for enemies and hazard team
// (keep numbers in ascending order in order for variant-capping to work)
const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS] =
{
"*anger1", //Say when acquire an enemy when didn't have one before
"*anger2",
"*anger3",
"*victory1", //Say when killed an enemy
"*victory2",
"*victory3",
"*confuse1", //Say when confused
"*confuse2",
"*confuse3",
"*pushed1", //Say when force-pushed
"*pushed2",
"*pushed3",
"*choke1",
"*choke2",
"*choke3",
"*ffwarn",
"*ffturn",
NULL
};
//Used as a supplement to the basic set for stormtroopers
// (keep numbers in ascending order in order for variant-capping to work)
const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] =
{
"*chase1",
"*chase2",
"*chase3",
"*cover1",
"*cover2",
"*cover3",
"*cover4",
"*cover5",
"*detected1",
"*detected2",
"*detected3",
"*detected4",
"*detected5",
"*lost1",
"*outflank1",
"*outflank2",
"*escaping1",
"*escaping2",
"*escaping3",
"*giveup1",
"*giveup2",
"*giveup3",
"*giveup4",
"*look1",
"*look2",
"*sight1",
"*sight2",
"*sight3",
"*sound1",
"*sound2",
"*sound3",
"*suspicious1",
"*suspicious2",
"*suspicious3",
"*suspicious4",
"*suspicious5",
NULL
};
//Used as a supplement to the basic set for jedi
// (keep numbers in ascending order in order for variant-capping to work)
const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS] =
{
"*combat1",
"*combat2",
"*combat3",
"*jdetected1",
"*jdetected2",
"*jdetected3",
"*taunt1",
"*taunt2",
"*taunt3",
"*jchase1",
"*jchase2",
"*jchase3",
"*jlost1",
"*jlost2",
"*jlost3",
"*deflect1",
"*deflect2",
"*deflect3",
"*gloat1",
"*gloat2",
"*gloat3",
"*pushfail",
NULL
};
//Used for DUEL taunts
const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS] =
{
"*anger1", //Say when acquire an enemy when didn't have one before
"*anger2",
"*anger3",
"*victory1", //Say when killed an enemy
"*victory2",
"*victory3",
"*taunt1",
"*taunt2",
"*taunt3",
"*deflect1",
"*deflect2",
"*deflect3",
"*gloat1",
"*gloat2",
"*gloat3",
NULL
};
void CG_Disintegration(centity_t *cent, refEntity_t *ent);
/*
================
CG_CustomSound
================
*/
sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) {
clientInfo_t *ci;
int i;
int numCSounds = 0;
int numCComSounds = 0;
int numCExSounds = 0;
int numCJediSounds = 0;
int numCSiegeSounds = 0;
int numCDuelSounds = 0;
char lSoundName[MAX_QPATH];
if ( soundName[0] != '*' ) {
return trap_S_RegisterSound( soundName );
}
COM_StripExtension(soundName, lSoundName, sizeof(lSoundName));
if ( clientNum < 0 )
{
clientNum = 0;
}
if (clientNum >= MAX_CLIENTS)
{
ci = cg_entities[clientNum].npcClient;
}
else
{
ci = &cgs.clientinfo[ clientNum ];
}
if (!ci)
{
return 0;
}
for (i = 0; i < MAX_CUSTOM_SOUNDS; i++)
{
if (!cg_customSoundNames[i])
{
numCSounds = i;
break;
}
}
if (clientNum >= MAX_CLIENTS)
{ //these are only for npc's
for (i = 0; i < MAX_CUSTOM_SOUNDS; i++)
{
if (!cg_customCombatSoundNames[i])
{
numCComSounds = i;
break;
}
}
for (i = 0; i < MAX_CUSTOM_SOUNDS; i++)
{
if (!cg_customExtraSoundNames[i])
{
numCExSounds = i;
break;
}
}
for (i = 0; i < MAX_CUSTOM_SOUNDS; i++)
{
if (!cg_customJediSoundNames[i])
{
numCJediSounds = i;
break;
}
}
}
if (cgs.gametype >= GT_TEAM || cg_buildScript.integer)
{ //siege only
for (i = 0; i < MAX_CUSTOM_SOUNDS; i++)
{
if (!bg_customSiegeSoundNames[i])
{
numCSiegeSounds = i;
break;
}
}
}
if (cgs.gametype == GT_DUEL
|| cgs.gametype == GT_POWERDUEL
|| cg_buildScript.integer)
{ //Duel only
for (i = 0; i < MAX_CUSTOM_SOUNDS; i++)
{
if (!cg_customDuelSoundNames[i])
{
numCDuelSounds = i;
break;
}
}
}
for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ )
{
if ( i < numCSounds && !strcmp( lSoundName, cg_customSoundNames[i] ) )
{
return ci->sounds[i];
}
else if ( (cgs.gametype >= GT_TEAM || cg_buildScript.integer) && i < numCSiegeSounds && !strcmp( lSoundName, bg_customSiegeSoundNames[i] ) )
{ //siege only
return ci->siegeSounds[i];
}
else if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) && i < numCDuelSounds && !strcmp( lSoundName, cg_customDuelSoundNames[i] ) )
{ //siege only
return ci->duelSounds[i];
}
else if ( clientNum >= MAX_CLIENTS && i < numCComSounds && !strcmp( lSoundName, cg_customCombatSoundNames[i] ) )
{ //npc only
return ci->combatSounds[i];
}
else if ( clientNum >= MAX_CLIENTS && i < numCExSounds && !strcmp( lSoundName, cg_customExtraSoundNames[i] ) )
{ //npc only
return ci->extraSounds[i];
}
else if ( clientNum >= MAX_CLIENTS && i < numCJediSounds && !strcmp( lSoundName, cg_customJediSoundNames[i] ) )
{ //npc only
return ci->jediSounds[i];
}
}
//CG_Error( "Unknown custom sound: %s", lSoundName );
#ifndef FINAL_BUILD
Com_Printf( "Unknown custom sound: %s", lSoundName );
#endif
return 0;
}
/*
=============================================================================
CLIENT INFO
=============================================================================
*/
#define MAX_SURF_LIST_SIZE 1024
qboolean CG_ParseSurfsFile( const char *modelName, const char *skinName, char *surfOff, char *surfOn )
{
const char *text_p;
int len;
const char *token;
const char *value;
char text[20000];
char sfilename[MAX_QPATH];
fileHandle_t f;
int i = 0;
while (skinName && skinName[i])
{
if (skinName[i] == '|')
{ //this is a multi-part skin, said skins do not support .surf files
return qfalse;
}
i++;
}
// Load and parse .surf file
Com_sprintf( sfilename, sizeof( sfilename ), "models/players/%s/model_%s.surf", modelName, skinName );
// load the file
len = trap_FS_FOpenFile( sfilename, &f, FS_READ );
if ( len <= 0 )
{//no file
return qfalse;
}
if ( len >= sizeof( text ) - 1 )
{
Com_Printf( "File %s too long\n", sfilename );
trap_FS_FCloseFile( f );
return qfalse;
}
trap_FS_Read( text, len, f );
text[len] = 0;
trap_FS_FCloseFile( f );
// parse the text
text_p = text;
memset( (char *)surfOff, 0, sizeof(surfOff) );
memset( (char *)surfOn, 0, sizeof(surfOn) );
// read information for surfOff and surfOn
while ( 1 )
{
token = COM_ParseExt( &text_p, qtrue );
if ( !token || !token[0] )
{
break;
}
// surfOff
if ( !Q_stricmp( token, "surfOff" ) )
{
if ( COM_ParseString( &text_p, &value ) )
{
continue;
}
if ( surfOff && surfOff[0] )
{
Q_strcat( surfOff, MAX_SURF_LIST_SIZE, "," );
Q_strcat( surfOff, MAX_SURF_LIST_SIZE, value );
}
else
{
Q_strncpyz( surfOff, value, MAX_SURF_LIST_SIZE );
}
continue;
}
// surfOn
if ( !Q_stricmp( token, "surfOn" ) )
{
if ( COM_ParseString( &text_p, &value ) )
{
continue;
}
if ( surfOn && surfOn[0] )
{
Q_strcat( surfOn, MAX_SURF_LIST_SIZE, ",");
Q_strcat( surfOn, MAX_SURF_LIST_SIZE, value );
}
else
{
Q_strncpyz( surfOn, value, MAX_SURF_LIST_SIZE );
}
continue;
}
}
return qtrue;
}
/*
==========================
CG_RegisterClientModelname
==========================
*/
#include "../namespace_begin.h"
qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName);
qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors );
#include "../namespace_end.h"
static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *teamName, int clientNum ) {
int handle;
char afilename[MAX_QPATH];
char /**GLAName,*/ *slash;
char GLAName[MAX_QPATH];
vec3_t tempVec = {0,0,0};
qboolean badModel = qfalse;
char surfOff[MAX_SURF_LIST_SIZE];
char surfOn[MAX_SURF_LIST_SIZE];
int checkSkin;
char *useSkinName;
retryModel:
if (badModel)
{
if (modelName && modelName[0])
{
Com_Printf("WARNING: Attempted to load an unsupported multiplayer model %s! (bad or missing bone, or missing animation sequence)\n", modelName);
}
modelName = "kyle";
skinName = "default";
badModel = qfalse;
}
// First things first. If this is a ghoul2 model, then let's make sure we demolish this first.
if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{
trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model));
}
if (!BG_IsValidCharacterModel(modelName, skinName))
{
modelName = "kyle";
skinName = "default";
}
if ( cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE )
{ //We won't force colors for siege.
BG_ValidateSkinForTeam( ci->modelName, ci->skinName, ci->team, ci->colorOverride );
skinName = ci->skinName;
}
else
{
ci->colorOverride[0] = ci->colorOverride[1] = ci->colorOverride[2] = 0.0f;
}
if (strchr(skinName, '|'))
{//three part skin
useSkinName = va("models/players/%s/|%s", modelName, skinName);
}
else
{
useSkinName = va("models/players/%s/model_%s.skin", modelName, skinName);
}
checkSkin = trap_R_RegisterSkin(useSkinName);
if (checkSkin)
{
ci->torsoSkin = checkSkin;
}
else
{ //fallback to the default skin
ci->torsoSkin = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName, skinName));
}
Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", modelName );
handle = trap_G2API_InitGhoul2Model(&ci->ghoul2Model, afilename, 0, ci->torsoSkin, 0, 0, 0);
if (handle<0)
{
return qfalse;
}
// The model is now loaded.
trap_G2API_SetSkin(ci->ghoul2Model, 0, ci->torsoSkin, ci->torsoSkin);
GLAName[0] = 0;
trap_G2API_GetGLAName( ci->ghoul2Model, 0, GLAName);
if (GLAName[0] != 0)
{
if (!strstr(GLAName, "players/_humanoid/") /*&&
(!strstr(GLAName, "players/rockettrooper/") || cgs.gametype != GT_SIEGE)*/) //only allow rockettrooper in siege
{ //Bad!
badModel = qtrue;
goto retryModel;
}
}
if (!BGPAFtextLoaded)
{
if (GLAName[0] == 0/*GLAName == NULL*/)
{
badModel = qtrue;
goto retryModel;
}
Q_strncpyz( afilename, GLAName, sizeof( afilename ));
slash = Q_strrchr( afilename, '/' );
if ( slash )
{
strcpy(slash, "/animation.cfg");
} // Now afilename holds just the path to the animation.cfg
else
{ // Didn't find any slashes, this is a raw filename right in base (whish isn't a good thing)
return qfalse;
}
//rww - All player models must use humanoid, no matter what.
if (Q_stricmp(afilename, "models/players/_humanoid/animation.cfg") /*&&
Q_stricmp(afilename, "models/players/rockettrooper/animation.cfg")*/)
{
Com_Printf( "Model does not use supported animation config.\n");
return qfalse;
}
else if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1)
{
Com_Printf( "Failed to load animation file models/players/_humanoid/animation.cfg\n" );
return qfalse;
}
BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); //get the sounds for the humanoid anims
// if (cgs.gametype == GT_SIEGE)
// {
// BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); //parse rockettrooper too
// }
//For the time being, we're going to have all real players use the generic humanoid soundset and that's it.
//Only npc's will use model-specific soundsets.
// BG_ParseAnimationSndFile(va("models/players/%s/", modelName), 0, -1);
}
else if (!bgAllEvents[0].eventsParsed)
{ //make sure the player anim sounds are loaded even if the anims already are
BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 );
// if (cgs.gametype == GT_SIEGE)
// {
// BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 );
// }
}
if ( CG_ParseSurfsFile( modelName, skinName, surfOff, surfOn ) )
{//turn on/off any surfs
const char *token;
const char *p;
//Now turn on/off any surfaces
if ( surfOff && surfOff[0] )
{
p = surfOff;
while ( 1 )
{
token = COM_ParseExt( &p, qtrue );
if ( !token[0] )
{//reached end of list
break;
}
//turn off this surf
trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0x00000002/*G2SURFACEFLAG_OFF*/ );
}
}
if ( surfOn && surfOn[0] )
{
p = surfOn;
while ( 1 )
{
token = COM_ParseExt( &p, qtrue );
if ( !token[0] )
{//reached end of list
break;
}
//turn on this surf
trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0 );
}
}
}
ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand");
if (!trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg.time, -1, -1))
{
badModel = qtrue;
}
if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time))
{
badModel = qtrue;
}
if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time))
{
badModel = qtrue;
}
ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand");
//rhand must always be first bolt. lhand always second. Whichever you want the
//jetpack bolted to must always be third.
trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg");
//claw bolts
trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand_cap_r_arm");
trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand_cap_l_arm");
ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top");
if (ci->bolt_head == -1)
{
ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow");
}
ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion");
//We need a lower lumbar bolt for footsteps
ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar");
if (ci->bolt_rhand == -1 || ci->bolt_lhand == -1 || ci->bolt_head == -1 || ci->bolt_motion == -1 || ci->bolt_llumbar == -1)
{
badModel = qtrue;
}
if (badModel)
{
goto retryModel;
}
if (!Q_stricmp(modelName, "boba_fett"))
{ //special case, turn off the jetpack surfs
trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_rjet", TURN_OFF);
trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_cjet", TURN_OFF);
trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_ljet", TURN_OFF);
}
// ent->s.radius = 90;
if (clientNum != -1)
{
/*
if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2))
{
trap_G2API_CleanGhoul2Models(&(cg_entities[clientNum].ghoul2));
}
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2);
*/
cg_entities[clientNum].ghoul2weapon = NULL;
}
Q_strncpyz (ci->teamName, teamName, sizeof(ci->teamName));
// Model icon for drawing the portrait on screen
ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/icon_%s", modelName, skinName ) );
if (!ci->modelIcon)
{
int i = 0;
int j;
char iconName[1024];
strcpy(iconName, "icon_");
j = strlen(iconName);
while (skinName[i] && skinName[i] != '|' && j < 1024)
{
iconName[j] = skinName[i];
j++;
i++;
}
iconName[j] = 0;
if (skinName[i] == '|')
{ //looks like it actually may be a custom model skin, let's try getting the icon...
ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/%s", modelName, iconName ) );
}
}
return qtrue;
}
/*
====================
CG_ColorFromString
====================
*/
static void CG_ColorFromString( const char *v, vec3_t color ) {
int val;
VectorClear( color );
val = atoi( v );
if ( val < 1 || val > 7 ) {
VectorSet( color, 1, 1, 1 );
return;
}
if ( val & 1 ) {
color[2] = 1.0f;
}
if ( val & 2 ) {
color[1] = 1.0f;
}
if ( val & 4 ) {
color[0] = 1.0f;
}
}
/*
====================
CG_ColorFromInt
====================
*/
static void CG_ColorFromInt( int val, vec3_t color ) {
VectorClear( color );
if ( val < 1 || val > 7 ) {
VectorSet( color, 1, 1, 1 );
return;
}
if ( val & 1 ) {
color[2] = 1.0f;
}
if ( val & 2 ) {
color[1] = 1.0f;
}
if ( val & 4 ) {
color[0] = 1.0f;
}
}
//load anim info
int CG_G2SkelForModel(void *g2)
{
int animIndex = -1;
char GLAName[MAX_QPATH];
char *slash;
GLAName[0] = 0;
trap_G2API_GetGLAName(g2, 0, GLAName);
slash = Q_strrchr( GLAName, '/' );
if ( slash )
{
strcpy(slash, "/animation.cfg");
animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse);
}
return animIndex;
}
//get the appropriate anim events file index
int CG_G2EvIndexForModel(void *g2, int animIndex)
{
int evtIndex = -1;
char GLAName[MAX_QPATH];
char *slash;
if (animIndex == -1)
{
assert(!"shouldn't happen, bad animIndex");
return -1;
}
GLAName[0] = 0;
trap_G2API_GetGLAName(g2, 0, GLAName);
slash = Q_strrchr( GLAName, '/' );
if ( slash )
{
slash++;
*slash = 0;
evtIndex = BG_ParseAnimationEvtFile(GLAName, animIndex, bgNumAnimEvents);
}
return evtIndex;
}
#define DEFAULT_FEMALE_SOUNDPATH "chars/mp_generic_female/misc"//"chars/tavion/misc"
#define DEFAULT_MALE_SOUNDPATH "chars/mp_generic_male/misc"//"chars/kyle/misc"
void CG_LoadCISounds(clientInfo_t *ci, qboolean modelloaded)
{
fileHandle_t f;
qboolean isFemale = qfalse;
int i = 0;
int fLen = 0;
const char *dir;
char soundpath[MAX_QPATH];
char soundName[MAX_QPATH];
const char *s;
dir = ci->modelName;
if ( !ci->skinName || !Q_stricmp( "default", ci->skinName ) )
{//try default sounds.cfg first
fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ);
if ( !f )
{//no? Look for _default sounds.cfg
fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_default.cfg", dir), &f, FS_READ);
}
}
else
{//use the .skin associated with this skin
fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_%s.cfg", dir, ci->skinName), &f, FS_READ);
if ( !f )
{//fall back to default sounds
fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ);
}
}
soundpath[0] = 0;
if (f)
{
trap_FS_Read(soundpath, fLen, f);
soundpath[fLen] = 0;
i = fLen;
while (i >= 0 && soundpath[i] != '\n')
{
if (soundpath[i] == 'f')
{
isFemale = qtrue;
soundpath[i] = 0;
}
i--;
}
i = 0;
while (soundpath[i] && soundpath[i] != '\r' && soundpath[i] != '\n')
{
i++;
}
soundpath[i] = 0;
trap_FS_FCloseFile(f);
}
if (isFemale)
{
ci->gender = GENDER_FEMALE;
}
else
{
ci->gender = GENDER_MALE;
}
trap_S_ShutUp(qtrue);
for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ )
{
s = cg_customSoundNames[i];
if ( !s ) {
break;
}
Com_sprintf(soundName, sizeof(soundName), "%s", s+1);
COM_StripExtension(soundName, soundName, sizeof(soundName));
//strip the extension because we might want .mp3's
ci->sounds[i] = 0;
// if the model didn't load use the sounds of the default model
if (soundpath[0])
{
ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) );
}
else
{
if (modelloaded)
{
ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) );
}
}
if (!ci->sounds[i])
{ //failed the load, try one out of the generic path
if (isFemale)
{
ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) );
}
else
{
ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) );
}
}
}
if (cgs.gametype >= GT_TEAM || cg_buildScript.integer)
{ //load the siege sounds then
for ( i = 0 ; i < MAX_CUSTOM_SIEGE_SOUNDS; i++ )
{
s = bg_customSiegeSoundNames[i];
if ( !s )
{
break;
}
Com_sprintf(soundName, sizeof(soundName), "%s", s+1);
COM_StripExtension(soundName, soundName, sizeof(soundName));
//strip the extension because we might want .mp3's
ci->siegeSounds[i] = 0;
// if the model didn't load use the sounds of the default model
if (soundpath[0])
{
ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", soundpath, soundName) );
}
else
{
if (modelloaded)
{
ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) );
}
}
if (!ci->siegeSounds[i])
{ //failed the load, try one out of the generic path
if (isFemale)
{
ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) );
}
else
{
ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) );
}
}
}
}
if (cgs.gametype == GT_DUEL
||cgs.gametype == GT_POWERDUEL
|| cg_buildScript.integer)
{ //load the Duel sounds then
for ( i = 0 ; i < MAX_CUSTOM_DUEL_SOUNDS; i++ )
{
s = cg_customDuelSoundNames[i];
if ( !s )
{
break;
}
Com_sprintf(soundName, sizeof(soundName), "%s", s+1);
COM_StripExtension(soundName, soundName, sizeof(soundName));
//strip the extension because we might want .mp3's
ci->duelSounds[i] = 0;
// if the model didn't load use the sounds of the default model
if (soundpath[0])
{
ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) );
}
else
{
if (modelloaded)
{
ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) );
}
}
if (!ci->duelSounds[i])
{ //failed the load, try one out of the generic path
if (isFemale)
{
ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) );
}
else
{
ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) );
}
}
}
}
trap_S_ShutUp(qfalse);
}
/*
===================
CG_LoadClientInfo
Load it now, taking the disk hits.
This will usually be deferred to a safe time
===================
*/
void CG_LoadClientInfo( clientInfo_t *ci ) {
qboolean modelloaded;
int clientNum;
int i;
char teamname[MAX_QPATH];
clientNum = ci - cgs.clientinfo;
if (clientNum < 0 || clientNum >= MAX_CLIENTS)
{
clientNum = -1;
}
ci->deferred = qfalse;
/*
if (ci->team == TEAM_SPECTATOR)
{
// reset any existing players and bodies, because they might be in bad
// frames for this new model
clientNum = ci - cgs.clientinfo;
for ( i = 0 ; i < MAX_GENTITIES ; i++ ) {
if ( cg_entities[i].currentState.clientNum == clientNum
&& cg_entities[i].currentState.eType == ET_PLAYER ) {
CG_ResetPlayerEntity( &cg_entities[i] );
}
}
if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{
trap_G2API_CleanGhoul2Models(&ci->ghoul2Model);
}
return;
}
*/
teamname[0] = 0;
if( cgs.gametype >= GT_TEAM) {
if( ci->team == TEAM_BLUE ) {
Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME/*cg_blueTeamName.string*/, sizeof(teamname) );
} else {
Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME/*cg_redTeamName.string*/, sizeof(teamname) );
}
}
if( teamname[0] ) {
strcat( teamname, "/" );
}
modelloaded = qtrue;
if (cgs.gametype == GT_SIEGE &&
(ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1))
{ //yeah.. kind of a hack I guess. Don't care until they are actually ingame with a valid class.
if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) )
{
CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL );
}
}
else
{
if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, teamname, clientNum ) ) {
//CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname );
//rww - DO NOT error out here! Someone could just type in a nonsense model name and crash everyone's client.
//Give it a chance to load default model for this client instead.
// fall back to default team name
if( cgs.gametype >= GT_TEAM) {
// keep skin name
if( ci->team == TEAM_BLUE ) {
Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) );
} else {
Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) );
}
if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, ci->skinName, teamname, -1 ) ) {
CG_Error( "DEFAULT_MODEL / skin (%s/%s) failed to register", DEFAULT_MODEL, ci->skinName );
}
} else {
if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) {
CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL );
}
}
modelloaded = qfalse;
}
}
if (clientNum != -1)
{
trap_G2API_ClearAttachedInstance(clientNum);
}
if (clientNum != -1 && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{
if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2))
{
trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2);
}
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2);
//Attach the instance to this entity num so we can make use of client-server
//shared operations if possible.
trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse);
if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1)
{ //check now to see if we have this bone for setting anims and such
cg_entities[clientNum].noFace = qtrue;
}
cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2);
cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex);
}
ci->newAnims = qfalse;
if ( ci->torsoModel ) {
orientation_t tag;
// if the torso model has the "tag_flag"
if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) {
ci->newAnims = qtrue;
}
}
// sounds
if (cgs.gametype == GT_SIEGE &&
(ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1))
{ //don't need to load sounds
}
else
{
CG_LoadCISounds(ci, modelloaded);
}
ci->deferred = qfalse;
// reset any existing players and bodies, because they might be in bad
// frames for this new model
clientNum = ci - cgs.clientinfo;
for ( i = 0 ; i < MAX_GENTITIES ; i++ ) {
if ( cg_entities[i].currentState.clientNum == clientNum
&& cg_entities[i].currentState.eType == ET_PLAYER ) {
CG_ResetPlayerEntity( &cg_entities[i] );
}
}
}
//Take care of initializing all the ghoul2 saber stuff based on clientinfo data. -rww
static void CG_InitG2SaberData(int saberNum, clientInfo_t *ci)
{
trap_G2API_InitGhoul2Model(&ci->ghoul2Weapons[saberNum], ci->saber[saberNum].model, 0, ci->saber[saberNum].skin, 0, 0, 0);
if (ci->ghoul2Weapons[saberNum])
{
int k = 0;
int tagBolt;
char *tagName;
if (ci->saber[saberNum].skin)
{
trap_G2API_SetSkin(ci->ghoul2Weapons[saberNum], 0, ci->saber[saberNum].skin, ci->saber[saberNum].skin);
}
if (ci->saber[saberNum].saberFlags & SFL_BOLT_TO_WRIST)
{
trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, 3+saberNum);
}
else
{
trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, saberNum);
}
while (k < ci->saber[saberNum].numBlades)
{
tagName = va("*blade%i", k+1);
tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, tagName);
if (tagBolt == -1)
{
if (k == 0)
{ //guess this is an 0ldsk3wl saber
tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, "*flash");
if (tagBolt == -1)
{
assert(0);
}
break;
}
if (tagBolt == -1)
{
assert(0);
break;
}
}
k++;
}
}
}
/*
======================
CG_CopyClientInfoModel
======================
*/
static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to )
{
VectorCopy( from->headOffset, to->headOffset );
// to->footsteps = from->footsteps;
to->gender = from->gender;
to->legsModel = from->legsModel;
to->legsSkin = from->legsSkin;
to->torsoModel = from->torsoModel;
to->torsoSkin = from->torsoSkin;
//to->headModel = from->headModel;
//to->headSkin = from->headSkin;
to->modelIcon = from->modelIcon;
to->newAnims = from->newAnims;
//to->ghoul2Model = from->ghoul2Model;
//rww - Trying to use the same ghoul2 pointer for two seperate clients == DISASTER
assert(to->ghoul2Model != from->ghoul2Model);
if (to->ghoul2Model && trap_G2_HaveWeGhoul2Models(to->ghoul2Model))
{
trap_G2API_CleanGhoul2Models(&to->ghoul2Model);
}
if (from->ghoul2Model && trap_G2_HaveWeGhoul2Models(from->ghoul2Model))
{
trap_G2API_DuplicateGhoul2Instance(from->ghoul2Model, &to->ghoul2Model);
}
//Don't do this, I guess. Just leave the saber info in the original, so it will be
//properly initialized.
/*
strcpy(to->saberName, from->saberName);
strcpy(to->saber2Name, from->saber2Name);
while (i < MAX_SABERS)
{
if (to->ghoul2Weapons[i] && trap_G2_HaveWeGhoul2Models(to->ghoul2Weapons[i]))
{
trap_G2API_CleanGhoul2Models(&to->ghoul2Weapons[i]);
}
WP_SetSaber(to->saber, 0, to->saberName);
WP_SetSaber(to->saber, 1, to->saber2Name);
j = 0;
while (j < MAX_SABERS)
{
if (to->saber[j].model[0])
{
CG_InitG2SaberData(j, to);
}
j++;
}
i++;
}
*/
to->bolt_head = from->bolt_head;
to->bolt_lhand = from->bolt_lhand;
to->bolt_rhand = from->bolt_rhand;
to->bolt_motion = from->bolt_motion;
to->bolt_llumbar = from->bolt_llumbar;
to->siegeIndex = from->siegeIndex;
memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
memcpy( to->siegeSounds, from->siegeSounds, sizeof( to->siegeSounds ) );
memcpy( to->duelSounds, from->duelSounds, sizeof( to->duelSounds ) );
}
/*
======================
CG_ScanForExistingClientInfo
======================
*/
static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci, int clientNum ) {
int i;
clientInfo_t *match;
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
match = &cgs.clientinfo[ i ];
if ( !match->infoValid ) {
continue;
}
if ( match->deferred ) {
continue;
}
if ( !Q_stricmp( ci->modelName, match->modelName )
&& !Q_stricmp( ci->skinName, match->skinName )
&& !Q_stricmp( ci->saberName, match->saberName)
&& !Q_stricmp( ci->saber2Name, match->saber2Name)
// && !Q_stricmp( ci->headModelName, match->headModelName )
// && !Q_stricmp( ci->headSkinName, match->headSkinName )
// && !Q_stricmp( ci->blueTeam, match->blueTeam )
// && !Q_stricmp( ci->redTeam, match->redTeam )
&& (cgs.gametype < GT_TEAM || ci->team == match->team)
&& ci->siegeIndex == match->siegeIndex
&& match->ghoul2Model
&& match->bolt_head) //if the bolts haven't been initialized, this "match" is useless to us
{
// this clientinfo is identical, so use it's handles
ci->deferred = qfalse;
//rww - Filthy hack. If this is actually the info already belonging to us, just reassign the pointer.
//Switching instances when not necessary produces small animation glitches.
//Actually, before, were we even freeing the instance attached to the old clientinfo before copying
//this new clientinfo over it? Could be a nasty leak possibility. (though this should remedy it in theory)
if (clientNum == i)
{
if (match->ghoul2Model && trap_G2_HaveWeGhoul2Models(match->ghoul2Model))
{ //The match has a valid instance (if it didn't, we'd probably already be fudged (^_^) at this state)
if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{ //First kill the copy we have if we have one. (but it should be null)
trap_G2API_CleanGhoul2Models(&ci->ghoul2Model);
}
VectorCopy( match->headOffset, ci->headOffset );
// ci->footsteps = match->footsteps;
ci->gender = match->gender;
ci->legsModel = match->legsModel;
ci->legsSkin = match->legsSkin;
ci->torsoModel = match->torsoModel;
ci->torsoSkin = match->torsoSkin;
ci->modelIcon = match->modelIcon;
ci->newAnims = match->newAnims;
ci->bolt_head = match->bolt_head;
ci->bolt_lhand = match->bolt_lhand;
ci->bolt_rhand = match->bolt_rhand;
ci->bolt_motion = match->bolt_motion;
ci->bolt_llumbar = match->bolt_llumbar;
ci->siegeIndex = match->siegeIndex;
memcpy( ci->sounds, match->sounds, sizeof( ci->sounds ) );
memcpy( ci->siegeSounds, match->siegeSounds, sizeof( ci->siegeSounds ) );
memcpy( ci->duelSounds, match->duelSounds, sizeof( ci->duelSounds ) );
//We can share this pointer, because it already belongs to this client.
//The pointer itself and the ghoul2 instance is never actually changed, just passed between
//clientinfo structures.
ci->ghoul2Model = match->ghoul2Model;
//Don't need to do this I guess, whenever this function is called the saber stuff should
//already be taken care of in the new info.
/*
while (k < MAX_SABERS)
{
if (match->ghoul2Weapons[k] && match->ghoul2Weapons[k] != ci->ghoul2Weapons[k])
{
if (ci->ghoul2Weapons[k])
{
trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]);
}
ci->ghoul2Weapons[k] = match->ghoul2Weapons[k];
}
k++;
}
*/
}
}
else
{
CG_CopyClientInfoModel( match, ci );
}
return qtrue;
}
}
// nothing matches, so defer the load
return qfalse;
}
/*
======================
CG_SetDeferredClientInfo
We aren't going to load it now, so grab some other
client's info to use until we have some spare time.
======================
*/
static void CG_SetDeferredClientInfo( clientInfo_t *ci ) {
int i;
clientInfo_t *match;
// if someone else is already the same models and skins we
// can just load the client info
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
match = &cgs.clientinfo[ i ];
if ( !match->infoValid || match->deferred ) {
continue;
}
if ( Q_stricmp( ci->skinName, match->skinName ) ||
Q_stricmp( ci->modelName, match->modelName ) ||
// Q_stricmp( ci->headModelName, match->headModelName ) ||
// Q_stricmp( ci->headSkinName, match->headSkinName ) ||
(cgs.gametype >= GT_TEAM && ci->team != match->team && ci->team != TEAM_SPECTATOR) ) {
continue;
}
/*
if (Q_stricmp(ci->saberName, match->saberName) ||
Q_stricmp(ci->saber2Name, match->saber2Name))
{
continue;
}
*/
// just load the real info cause it uses the same models and skins
CG_LoadClientInfo( ci );
return;
}
// if we are in teamplay, only grab a model if the skin is correct
if ( cgs.gametype >= GT_TEAM ) {
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
match = &cgs.clientinfo[ i ];
if ( !match->infoValid || match->deferred ) {
continue;
}
if ( ci->team != TEAM_SPECTATOR &&
(Q_stricmp( ci->skinName, match->skinName ) ||
(cgs.gametype >= GT_TEAM && ci->team != match->team)) ) {
continue;
}
/*
if (Q_stricmp(ci->saberName, match->saberName) ||
Q_stricmp(ci->saber2Name, match->saber2Name))
{
continue;
}
*/
ci->deferred = qtrue;
CG_CopyClientInfoModel( match, ci );
return;
}
// load the full model, because we don't ever want to show
// an improper team skin. This will cause a hitch for the first
// player, when the second enters. Combat shouldn't be going on
// yet, so it shouldn't matter
CG_LoadClientInfo( ci );
return;
}
// find the first valid clientinfo and grab its stuff
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
match = &cgs.clientinfo[ i ];
if ( !match->infoValid ) {
continue;
}
/*
if (Q_stricmp(ci->saberName, match->saberName) ||
Q_stricmp(ci->saber2Name, match->saber2Name))
{
continue;
}
*/
if (match->deferred)
{ //no deferring off of deferred info. Because I said so.
continue;
}
ci->deferred = qtrue;
CG_CopyClientInfoModel( match, ci );
return;
}
// we should never get here...
//CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" );
//Actually it is possible now because of the unique sabers.
CG_LoadClientInfo( ci );
}
/*
======================
CG_NewClientInfo
======================
*/
#include "../namespace_begin.h"
void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName );
#include "../namespace_end.h"
void CG_NewClientInfo( int clientNum, qboolean entitiesInitialized ) {
clientInfo_t *ci;
clientInfo_t newInfo;
const char *configstring;
const char *v;
char *slash;
void *oldGhoul2;
void *oldG2Weapons[MAX_SABERS];
int i = 0;
int k = 0;
qboolean saberUpdate[MAX_SABERS];
ci = &cgs.clientinfo[clientNum];
oldGhoul2 = ci->ghoul2Model;
while (k < MAX_SABERS)
{
oldG2Weapons[k] = ci->ghoul2Weapons[k];
k++;
}
configstring = CG_ConfigString( clientNum + CS_PLAYERS );
if ( !configstring[0] ) {
if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{ //clean this stuff up first
trap_G2API_CleanGhoul2Models(&ci->ghoul2Model);
}
k = 0;
while (k < MAX_SABERS)
{
if (ci->ghoul2Weapons[k] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[k]))
{
trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]);
}
k++;
}
memset( ci, 0, sizeof( *ci ) );
return; // player just left
}
// build into a temp buffer so the defer checks can use
// the old value
memset( &newInfo, 0, sizeof( newInfo ) );
// isolate the player's name
v = Info_ValueForKey(configstring, "n");
Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );
// colors
v = Info_ValueForKey( configstring, "c1" );
CG_ColorFromString( v, newInfo.color1 );
newInfo.icolor1 = atoi(v);
v = Info_ValueForKey( configstring, "c2" );
CG_ColorFromString( v, newInfo.color2 );
newInfo.icolor2 = atoi(v);
// bot skill
v = Info_ValueForKey( configstring, "skill" );
newInfo.botSkill = atoi( v );
// handicap
v = Info_ValueForKey( configstring, "hc" );
newInfo.handicap = atoi( v );
// wins
v = Info_ValueForKey( configstring, "w" );
newInfo.wins = atoi( v );
// losses
v = Info_ValueForKey( configstring, "l" );
newInfo.losses = atoi( v );
// team
v = Info_ValueForKey( configstring, "t" );
newInfo.team = atoi( v );
//copy team info out to menu
if ( clientNum == cg.clientNum) //this is me
{
trap_Cvar_Set("ui_team", v);
}
// team task
v = Info_ValueForKey( configstring, "tt" );
newInfo.teamTask = atoi(v);
// team leader
v = Info_ValueForKey( configstring, "tl" );
newInfo.teamLeader = atoi(v);
// v = Info_ValueForKey( configstring, "g_redteam" );
// Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME);
// v = Info_ValueForKey( configstring, "g_blueteam" );
// Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME);
// model
v = Info_ValueForKey( configstring, "model" );
if ( cg_forceModel.integer ) {
// forcemodel makes everyone use a single model
// to prevent load hitches
char modelStr[MAX_QPATH];
char *skin;
trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) );
if ( ( skin = strchr( modelStr, '/' ) ) == NULL) {
skin = "default";
} else {
*skin++ = 0;
}
Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) );
Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) );
if ( cgs.gametype >= GT_TEAM ) {
// keep skin name
slash = strchr( v, '/' );
if ( slash ) {
Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
}
}
} else {
Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) );
slash = strchr( newInfo.modelName, '/' );
if ( !slash ) {
// modelName didn not include a skin name
Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
} else {
Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
// truncate modelName
*slash = 0;
}
}
if (cgs.gametype == GT_SIEGE)
{ //entries only sent in siege mode
//siege desired team
v = Info_ValueForKey( configstring, "sdt" );
if (v && v[0])
{
newInfo.siegeDesiredTeam = atoi(v);
}
else
{
newInfo.siegeDesiredTeam = 0;
}
//siege classname
v = Info_ValueForKey( configstring, "siegeclass" );
newInfo.siegeIndex = -1;
if (v)
{
siegeClass_t *siegeClass = BG_SiegeFindClassByName(v);
if (siegeClass)
{ //See if this class forces a model, if so, then use it. Same for skin.
newInfo.siegeIndex = BG_SiegeFindClassIndexByName(v);
if (siegeClass->forcedModel[0])
{
Q_strncpyz( newInfo.modelName, siegeClass->forcedModel, sizeof( newInfo.modelName ) );
}
if (siegeClass->forcedSkin[0])
{
Q_strncpyz( newInfo.skinName, siegeClass->forcedSkin, sizeof( newInfo.skinName ) );
}
if (siegeClass->hasForcedSaberColor)
{
newInfo.icolor1 = siegeClass->forcedSaberColor;
CG_ColorFromInt( newInfo.icolor1, newInfo.color1 );
}
if (siegeClass->hasForcedSaber2Color)
{
newInfo.icolor2 = siegeClass->forcedSaber2Color;
CG_ColorFromInt( newInfo.icolor2, newInfo.color2 );
}
}
}
}
saberUpdate[0] = qfalse;
saberUpdate[1] = qfalse;
//saber being used
v = Info_ValueForKey( configstring, "st" );
if (v && Q_stricmp(v, ci->saberName))
{
Q_strncpyz( newInfo.saberName, v, 64 );
WP_SetSaber(clientNum, newInfo.saber, 0, newInfo.saberName);
saberUpdate[0] = qtrue;
}
else
{
Q_strncpyz( newInfo.saberName, ci->saberName, 64 );
memcpy(&newInfo.saber[0], &ci->saber[0], sizeof(newInfo.saber[0]));
newInfo.ghoul2Weapons[0] = ci->ghoul2Weapons[0];
}
v = Info_ValueForKey( configstring, "st2" );
if (v && Q_stricmp(v, ci->saber2Name))
{
Q_strncpyz( newInfo.saber2Name, v, 64 );
WP_SetSaber(clientNum, newInfo.saber, 1, newInfo.saber2Name);
saberUpdate[1] = qtrue;
}
else
{
Q_strncpyz( newInfo.saber2Name, ci->saber2Name, 64 );
memcpy(&newInfo.saber[1], &ci->saber[1], sizeof(newInfo.saber[1]));
newInfo.ghoul2Weapons[1] = ci->ghoul2Weapons[1];
}
if (saberUpdate[0] || saberUpdate[1])
{
int j = 0;
while (j < MAX_SABERS)
{
if (saberUpdate[j])
{
if (newInfo.saber[j].model[0])
{
if (oldG2Weapons[j])
{ //free the old instance(s)
trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]);
oldG2Weapons[j] = 0;
}
CG_InitG2SaberData(j, &newInfo);
}
else
{
if (oldG2Weapons[j])
{ //free the old instance(s)
trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]);
oldG2Weapons[j] = 0;
}
}
cg_entities[clientNum].weapon = 0;
cg_entities[clientNum].ghoul2weapon = NULL; //force a refresh
}
j++;
}
}
//Check for any sabers that didn't get set again, if they didn't, then reassign the pointers for the new ci
k = 0;
while (k < MAX_SABERS)
{
if (oldG2Weapons[k])
{
newInfo.ghoul2Weapons[k] = oldG2Weapons[k];
}
k++;
}
//duel team
v = Info_ValueForKey( configstring, "dt" );
if (v)
{
newInfo.duelTeam = atoi(v);
}
else
{
newInfo.duelTeam = 0;
}
// force powers
v = Info_ValueForKey( configstring, "forcepowers" );
Q_strncpyz( newInfo.forcePowers, v, sizeof( newInfo.forcePowers ) );
if (cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE )
{ //We won't force colors for siege.
BG_ValidateSkinForTeam( newInfo.modelName, newInfo.skinName, newInfo.team, newInfo.colorOverride );
}
else
{
newInfo.colorOverride[0] = newInfo.colorOverride[1] = newInfo.colorOverride[2] = 0.0f;
}
// scan for an existing clientinfo that matches this modelname
// so we can avoid loading checks if possible
if ( !CG_ScanForExistingClientInfo( &newInfo, clientNum ) ) {
// if we are defering loads, just have it pick the first valid
if (cg.snap && cg.snap->ps.clientNum == clientNum)
{ //rww - don't defer your own client info ever
CG_LoadClientInfo( &newInfo );
}
else if ( cg_deferPlayers.integer && cgs.gametype != GT_SIEGE && !cg_buildScript.integer && !cg.loading ) {
// keep whatever they had if it won't violate team skins
CG_SetDeferredClientInfo( &newInfo );
} else {
CG_LoadClientInfo( &newInfo );
}
}
// replace whatever was there with the new one
newInfo.infoValid = qtrue;
if (ci->ghoul2Model &&
ci->ghoul2Model != newInfo.ghoul2Model &&
trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{ //We must kill this instance before we remove our only pointer to it from the cgame.
//Otherwise we will end up with extra instances all over the place, I think.
trap_G2API_CleanGhoul2Models(&ci->ghoul2Model);
}
*ci = newInfo;
//force a weapon change anyway, for all clients being rendered to the current client
while (i < MAX_CLIENTS)
{
cg_entities[i].ghoul2weapon = NULL;
i++;
}
if (clientNum != -1)
{ //don't want it using an invalid pointer to share
trap_G2API_ClearAttachedInstance(clientNum);
}
// Check if the ghoul2 model changed in any way. This is safer than assuming we have a legal cent shile loading info.
if (entitiesInitialized && ci->ghoul2Model && (oldGhoul2 != ci->ghoul2Model))
{ // Copy the new ghoul2 model to the centity.
animation_t *anim;
centity_t *cent = &cg_entities[clientNum];
anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.legsAnim) ];
if (anim)
{
int flags = BONE_ANIM_OVERRIDE_FREEZE;
int firstFrame = anim->firstFrame;
int setFrame = -1;
float animSpeed = 50.0f / anim->frameLerp;
if (anim->loopFrames != -1)
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames))
{
setFrame = cent->pe.legs.frame;
}
//rww - Set the animation again because it just got reset due to the model change
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150);
cg_entities[clientNum].currentState.legsAnim = 0;
}
anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.torsoAnim) ];
if (anim)
{
int flags = BONE_ANIM_OVERRIDE_FREEZE;
int firstFrame = anim->firstFrame;
int setFrame = -1;
float animSpeed = 50.0f / anim->frameLerp;
if (anim->loopFrames != -1)
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames))
{
setFrame = cent->pe.torso.frame;
}
//rww - Set the animation again because it just got reset due to the model change
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150);
cg_entities[clientNum].currentState.torsoAnim = 0;
}
if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2))
{
trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2);
}
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2);
if (clientNum != -1)
{
//Attach the instance to this entity num so we can make use of client-server
//shared operations if possible.
trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse);
}
if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1)
{ //check now to see if we have this bone for setting anims and such
cg_entities[clientNum].noFace = qtrue;
}
cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2);
cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex);
if (cg_entities[clientNum].currentState.number != cg.predictedPlayerState.clientNum &&
cg_entities[clientNum].currentState.weapon == WP_SABER)
{
cg_entities[clientNum].weapon = cg_entities[clientNum].currentState.weapon;
if (cg_entities[clientNum].ghoul2 && ci->ghoul2Model)
{
CG_CopyG2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon, cg_entities[clientNum].ghoul2);
cg_entities[clientNum].ghoul2weapon = CG_G2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon);
}
if (!cg_entities[clientNum].currentState.saberHolstered)
{ //if not holstered set length and desired length for both blades to full right now.
int j;
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
i = 0;
while (i < MAX_SABERS)
{
j = 0;
while (j < ci->saber[i].numBlades)
{
ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax;
j++;
}
i++;
}
}
}
}
}
qboolean cgQueueLoad = qfalse;
/*
======================
CG_ActualLoadDeferredPlayers
Called at the beginning of CG_Player if cgQueueLoad is set.
======================
*/
void CG_ActualLoadDeferredPlayers( void )
{
int i;
clientInfo_t *ci;
// scan for a deferred player to load
for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) {
if ( ci->infoValid && ci->deferred ) {
CG_LoadClientInfo( ci );
// break;
}
}
}
/*
======================
CG_LoadDeferredPlayers
Called each frame when a player is dead
and the scoreboard is up
so deferred players can be loaded
======================
*/
void CG_LoadDeferredPlayers( void ) {
cgQueueLoad = qtrue;
}
/*
=============================================================================
PLAYER ANIMATION
=============================================================================
*/
#define FOOTSTEP_DISTANCE 32
static void _PlayerFootStep( const vec3_t origin,
const float orientation,
const float radius,
centity_t *const cent, footstepType_t footStepType )
{
vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2};
trace_t trace;
footstep_t soundType = FOOTSTEP_TOTAL;
qboolean bMark = qfalse;
qhandle_t footMarkShader;
int effectID = -1;
//float alpha;
// send a trace down from the player to the ground
VectorCopy( origin, end );
end[2] -= FOOTSTEP_DISTANCE;
trap_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID );
// no shadow if too high
if ( trace.fraction >= 1.0f )
{
return;
}
//check for foot-steppable surface flag
switch( trace.surfaceFlags & MATERIAL_MASK )
{
case MATERIAL_MUD:
bMark = qtrue;
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_MUDRUN;
} else {
soundType = FOOTSTEP_MUDWALK;
}
effectID = cgs.effects.footstepMud;
break;
case MATERIAL_DIRT:
bMark = qtrue;
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_DIRTRUN;
} else {
soundType = FOOTSTEP_DIRTWALK;
}
effectID = cgs.effects.footstepSand;
break;
case MATERIAL_SAND:
bMark = qtrue;
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_SANDRUN;
} else {
soundType = FOOTSTEP_SANDWALK;
}
effectID = cgs.effects.footstepSand;
break;
case MATERIAL_SNOW:
bMark = qtrue;
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_SNOWRUN;
} else {
soundType = FOOTSTEP_SNOWWALK;
}
effectID = cgs.effects.footstepSnow;
break;
case MATERIAL_SHORTGRASS:
case MATERIAL_LONGGRASS:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_GRASSRUN;
} else {
soundType = FOOTSTEP_GRASSWALK;
}
break;
case MATERIAL_SOLIDMETAL:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_METALRUN;
} else {
soundType = FOOTSTEP_METALWALK;
}
break;
case MATERIAL_HOLLOWMETAL:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_PIPERUN;
} else {
soundType = FOOTSTEP_PIPEWALK;
}
break;
case MATERIAL_GRAVEL:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_GRAVELRUN;
} else {
soundType = FOOTSTEP_GRAVELWALK;
}
effectID = cgs.effects.footstepGravel;
break;
case MATERIAL_CARPET:
case MATERIAL_FABRIC:
case MATERIAL_CANVAS:
case MATERIAL_RUBBER:
case MATERIAL_PLASTIC:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_RUGRUN;
} else {
soundType = FOOTSTEP_RUGWALK;
}
break;
case MATERIAL_SOLIDWOOD:
case MATERIAL_HOLLOWWOOD:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_WOODRUN;
} else {
soundType = FOOTSTEP_WOODWALK;
}
break;
default:
//fall through
case MATERIAL_GLASS:
case MATERIAL_WATER:
case MATERIAL_FLESH:
case MATERIAL_BPGLASS:
case MATERIAL_DRYLEAVES:
case MATERIAL_GREENLEAVES:
case MATERIAL_TILES:
case MATERIAL_PLASTER:
case MATERIAL_SHATTERGLASS:
case MATERIAL_ARMOR:
case MATERIAL_COMPUTER:
case MATERIAL_CONCRETE:
case MATERIAL_ROCK:
case MATERIAL_ICE:
case MATERIAL_MARBLE:
if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
soundType = FOOTSTEP_STONERUN;
} else {
soundType = FOOTSTEP_STONEWALK;
}
break;
}
if (soundType < FOOTSTEP_TOTAL)
{
trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_BODY, cgs.media.footsteps[soundType][rand()&3] );
}
if ( cg_footsteps.integer < 4 )
{//debugging - 4 always does footstep effect
if ( cg_footsteps.integer < 2 ) //1 for sounds, 2 for effects, 3 for marks
{
return;
}
}
if ( effectID != -1 )
{
trap_FX_PlayEffectID( effectID, trace.endpos, trace.plane.normal, -1, -1 );
}
if ( cg_footsteps.integer < 4 )
{//debugging - 4 always does footstep effect
if ( !bMark || cg_footsteps.integer < 3 ) //1 for sounds, 2 for effects, 3 for marks
{
return;
}
}
switch ( footStepType )
{
case FOOTSTEP_HEAVY_R:
footMarkShader = cgs.media.fshrMarkShader;
break;
case FOOTSTEP_HEAVY_L:
footMarkShader = cgs.media.fshlMarkShader;
break;
case FOOTSTEP_R:
footMarkShader = cgs.media.fsrMarkShader;
break;
default:
case FOOTSTEP_L:
footMarkShader = cgs.media.fslMarkShader;
break;
}
// 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
if (trace.plane.normal[0] || trace.plane.normal[1] || trace.plane.normal[2])
{
CG_ImpactMark( footMarkShader, trace.endpos, trace.plane.normal,
orientation, 1,1,1, 1.0f, qfalse, radius, qfalse );
}
}
static void CG_PlayerFootsteps( centity_t *cent, footstepType_t footStepType )
{
if ( !cg_footsteps.integer )
{
return;
}
//FIXME: make this a feature of NPCs in the NPCs.cfg? Specify a footstep shader, if any?
if ( cent->currentState.NPC_class != CLASS_ATST
&& cent->currentState.NPC_class != CLASS_CLAW
&& cent->currentState.NPC_class != CLASS_FISH
&& cent->currentState.NPC_class != CLASS_FLIER2
&& cent->currentState.NPC_class != CLASS_GLIDER
&& cent->currentState.NPC_class != CLASS_INTERROGATOR
&& cent->currentState.NPC_class != CLASS_MURJJ
&& cent->currentState.NPC_class != CLASS_PROBE
&& cent->currentState.NPC_class != CLASS_R2D2
&& cent->currentState.NPC_class != CLASS_R5D2
&& cent->currentState.NPC_class != CLASS_REMOTE
&& cent->currentState.NPC_class != CLASS_SEEKER
&& cent->currentState.NPC_class != CLASS_SENTRY
&& cent->currentState.NPC_class != CLASS_SWAMP )
{
mdxaBone_t boltMatrix;
vec3_t tempAngles, sideOrigin;
int footBolt = -1;
tempAngles[PITCH] = 0;
tempAngles[YAW] = cent->pe.legs.yawAngle;
tempAngles[ROLL] = 0;
switch ( footStepType )
{
case FOOTSTEP_R:
case FOOTSTEP_HEAVY_R:
footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot");//cent->gent->footRBolt;
break;
case FOOTSTEP_L:
case FOOTSTEP_HEAVY_L:
default:
footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot");//cent->gent->footLBolt;
break;
}
//FIXME: get yaw orientation of the foot and use on decal
trap_G2API_GetBoltMatrix( cent->ghoul2, 0, footBolt, &boltMatrix, tempAngles, cent->lerpOrigin,
cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, sideOrigin );
sideOrigin[2] += 15; //fudge up a bit for coplanar
_PlayerFootStep( sideOrigin, cent->pe.legs.yawAngle, 6, cent, footStepType );
}
}
void CG_PlayerAnimEventDo( centity_t *cent, animevent_t *animEvent )
{
soundChannel_t channel = CHAN_AUTO;
clientInfo_t *client = NULL;
qhandle_t swingSound = 0;
qhandle_t spinSound = 0;
if ( !cent || !animEvent )
{
return;
}
switch ( animEvent->eventType )
{
case AEV_SOUNDCHAN:
channel = (soundChannel_t)animEvent->eventData[AED_SOUNDCHANNEL];
case AEV_SOUND:
{ // are there variations on the sound?
const int holdSnd = animEvent->eventData[ AED_SOUNDINDEX_START+Q_irand( 0, animEvent->eventData[AED_SOUND_NUMRANDOMSNDS] ) ];
if ( holdSnd > 0 )
{
trap_S_StartSound( NULL, cent->currentState.number, channel, holdSnd );
}
}
break;
case AEV_SABER_SWING:
if (cent->currentState.eType == ET_NPC)
{
client = cent->npcClient;
assert(client);
}
else
{
client = &cgs.clientinfo[cent->currentState.clientNum];
}
if ( client && client->infoValid && client->saber[animEvent->eventData[AED_SABER_SWING_SABERNUM]].swingSound[0] )
{//custom swing sound
swingSound = client->saber[0].swingSound[Q_irand(0,2)];
}
else
{
int randomSwing = 1;
switch ( animEvent->eventData[AED_SABER_SWING_TYPE] )
{
default:
case 0://SWING_FAST
randomSwing = Q_irand( 1, 3 );
break;
case 1://SWING_MEDIUM
randomSwing = Q_irand( 4, 6 );
break;
case 2://SWING_STRONG
randomSwing = Q_irand( 7, 9 );
break;
}
swingSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", randomSwing));
}
trap_S_StartSound(cent->currentState.pos.trBase, cent->currentState.number, CHAN_AUTO, swingSound );
break;
case AEV_SABER_SPIN:
if (cent->currentState.eType == ET_NPC)
{
client = cent->npcClient;
assert(client);
}
else
{
client = &cgs.clientinfo[cent->currentState.clientNum];
}
if ( client
&& client->infoValid
&& client->saber[AED_SABER_SPIN_SABERNUM].spinSound )
{//use override
spinSound = client->saber[AED_SABER_SPIN_SABERNUM].spinSound;
}
else
{
switch ( animEvent->eventData[AED_SABER_SPIN_TYPE] )
{
case 0://saberspinoff
spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspinoff.wav" );
break;
case 1://saberspin
spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin.wav" );
break;
case 2://saberspin1
spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" );
break;
case 3://saberspin2
spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" );
break;
case 4://saberspin3
spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" );
break;
default://random saberspin1-3
spinSound = trap_S_RegisterSound( va( "sound/weapons/saber/saberspin%d.wav", Q_irand(1,3)) );
break;
}
}
if ( spinSound )
{
trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_AUTO, spinSound );
}
break;
case AEV_FOOTSTEP:
CG_PlayerFootsteps( cent, (footstepType_t)animEvent->eventData[AED_FOOTSTEP_TYPE] );
break;
case AEV_EFFECT:
#if 0 //SP method
//add bolt, play effect
if ( animEvent->stringData != NULL && cent && cent->gent && cent->gent->ghoul2.size() )
{//have a bolt name we want to use
animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( &cent->gent->ghoul2[cent->gent->playerModel], animEvent->stringData );
animEvent->stringData = NULL;//so we don't try to do this again
}
if ( animEvent->eventData[AED_BOLTINDEX] != -1 )
{//have a bolt we want to play the effect on
G_PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->gent->playerModel, animEvent->eventData[AED_BOLTINDEX], cent->currentState.clientNum );
}
else
{//play at origin? FIXME: maybe allow a fwd/rt/up offset?
theFxScheduler.PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, qfalse );
}
#else //my method
if (animEvent->stringData && animEvent->stringData[0] && cent && cent->ghoul2)
{
animEvent->eventData[AED_MODELINDEX] = 0;
if ( ( Q_stricmpn( "*blade", animEvent->stringData, 6 ) == 0
|| Q_stricmp( "*flash", animEvent->stringData ) == 0 ) )
{//must be a weapon, try weapon 0?
animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 1, animEvent->stringData );
if ( animEvent->eventData[AED_BOLTINDEX] != -1 )
{//found it!
animEvent->eventData[AED_MODELINDEX] = 1;
}
else
{//hmm, just try on the player model, then?
animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData );
}
}
else
{
animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData );
}
animEvent->stringData[0] = 0;
}
if ( animEvent->eventData[AED_BOLTINDEX] != -1 )
{
vec3_t lAngles;
vec3_t bPoint, bAngle;
mdxaBone_t matrix;
VectorSet(lAngles, 0, cent->lerpAngles[YAW], 0);
trap_G2API_GetBoltMatrix(cent->ghoul2, animEvent->eventData[AED_MODELINDEX], animEvent->eventData[AED_BOLTINDEX], &matrix, lAngles,
cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bPoint);
VectorSet(bAngle, 0, 1, 0);
trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], bPoint, bAngle, -1, -1);
}
else
{
vec3_t bAngle;
VectorSet(bAngle, 0, 1, 0);
trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, bAngle, -1, -1);
}
#endif
break;
//Would have to keep track of this on server to for these, it's not worth it.
case AEV_FIRE:
case AEV_MOVE:
break;
/*
case AEV_FIRE:
//add fire event
if ( animEvent->eventData[AED_FIRE_ALT] )
{
G_AddEvent( cent->gent, EV_ALT_FIRE, 0 );
}
else
{
G_AddEvent( cent->gent, EV_FIRE_WEAPON, 0 );
}
break;
case AEV_MOVE:
//make him jump
if ( cent && cent->gent && cent->gent->client )
{
if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//on something
vec3_t fwd, rt, up, angles = {0, cent->gent->client->ps.viewangles[YAW], 0};
AngleVectors( angles, fwd, rt, up );
//FIXME: set or add to velocity?
VectorScale( fwd, animEvent->eventData[AED_MOVE_FWD], cent->gent->client->ps.velocity );
VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_RT], rt, cent->gent->client->ps.velocity );
VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_UP], up, cent->gent->client->ps.velocity );
if ( animEvent->eventData[AED_MOVE_UP] > 0 )
{//a jump
cent->gent->client->ps.pm_flags |= PMF_JUMPING;
G_AddEvent( cent->gent, EV_JUMP, 0 );
//FIXME: if have force jump, do this? or specify sound in the event data?
//cent->gent->client->ps.forceJumpZStart = cent->gent->client->ps.origin[2];//so we don't take damage if we land at same height
//G_SoundOnEnt( cent->gent, CHAN_BODY, "sound/weapons/force/jump.wav" );
}
}
}
break;
*/
default:
return;
break;
}
}
/*
void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, const vec3_t org, int entNum )
play any keyframed sounds - only when start a new frame
This func is called once for legs and once for torso
*/
void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, int entNum )
{
int i;
int firstFrame = 0, lastFrame = 0;
qboolean doEvent = qfalse, inSameAnim = qfalse, loopAnim = qfalse, match = qfalse, animBackward = qfalse;
animevent_t *animEvents = NULL;
if ( torso )
{
animEvents = bgAllEvents[eventFileIndex].torsoAnimEvents;
}
else
{
animEvents = bgAllEvents[eventFileIndex].legsAnimEvents;
}
if ( fabs((float)(oldFrame-frame)) > 1 )
{//given a range, see if keyFrame falls in that range
int oldAnim, anim;
if ( torso )
{
/*
if ( cg_reliableAnimSounds.integer > 1 )
{//more precise, slower
oldAnim = PM_TorsoAnimForFrame( &g_entities[entNum], oldFrame );
anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame );
}
else
*/
{//less precise, but faster
oldAnim = cg_entities[entNum].currentState.torsoAnim;
anim = cg_entities[entNum].nextState.torsoAnim;
}
}
else
{
/*
if ( cg_reliableAnimSounds.integer > 1 )
{//more precise, slower
oldAnim = PM_LegsAnimForFrame( &g_entities[entNum], oldFrame );
anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame );
}
else
*/
{//less precise, but faster
oldAnim = cg_entities[entNum].currentState.legsAnim;
anim = cg_entities[entNum].nextState.legsAnim;
}
}
if ( anim != oldAnim )
{//not in same anim
inSameAnim = qfalse;
//FIXME: we *could* see if the oldFrame was *just about* to play the keyframed sound...
}
else
{//still in same anim, check for looping anim
animation_t *animation;
inSameAnim = qtrue;
animation = &bgAllAnims[animFileIndex].anims[anim];
animBackward = (animation->frameLerp<0);
if ( animation->loopFrames != -1 )
{//a looping anim!
loopAnim = qtrue;
firstFrame = animation->firstFrame;
lastFrame = animation->firstFrame+animation->numFrames;
}
}
}
// Check for anim sound
for (i=0;i<MAX_ANIM_EVENTS;++i)
{
if ( animEvents[i].eventType == AEV_NONE ) // No event, end of list
{
break;
}
match = qfalse;
if ( animEvents[i].keyFrame == frame )
{//exact match
match = qtrue;
}
else if ( fabs((float)(oldFrame-frame)) > 1 /*&& cg_reliableAnimSounds.integer*/ )
{//given a range, see if keyFrame falls in that range
if ( inSameAnim )
{//if changed anims altogether, sorry, the sound is lost
if ( fabs((float)(oldFrame-animEvents[i].keyFrame)) <= 3
|| fabs((float)(frame-animEvents[i].keyFrame)) <= 3 )
{//must be at least close to the keyframe
if ( animBackward )
{//animation plays backwards
if ( oldFrame > animEvents[i].keyFrame && frame < animEvents[i].keyFrame )
{//old to new passed through keyframe
match = qtrue;
}
else if ( loopAnim )
{//hmm, didn't pass through it linearally, see if we looped
if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame )
{//keyframe is in this anim
if ( oldFrame > animEvents[i].keyFrame
&& frame > oldFrame )
{//old to new passed through keyframe
match = qtrue;
}
}
}
}
else
{//anim plays forwards
if ( oldFrame < animEvents[i].keyFrame && frame > animEvents[i].keyFrame )
{//old to new passed through keyframe
match = qtrue;
}
else if ( loopAnim )
{//hmm, didn't pass through it linearally, see if we looped
if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame )
{//keyframe is in this anim
if ( oldFrame < animEvents[i].keyFrame
&& frame < oldFrame )
{//old to new passed through keyframe
match = qtrue;
}
}
}
}
}
}
}
if ( match )
{
switch ( animEvents[i].eventType )
{
case AEV_SOUND:
case AEV_SOUNDCHAN:
// Determine probability of playing sound
if (!animEvents[i].eventData[AED_SOUND_PROBABILITY]) // 100%
{
doEvent = qtrue;
}
else if (animEvents[i].eventData[AED_SOUND_PROBABILITY] > Q_irand(0, 99) )
{
doEvent = qtrue;
}
break;
case AEV_SABER_SWING:
// Determine probability of playing sound
if (!animEvents[i].eventData[AED_SABER_SWING_PROBABILITY]) // 100%
{
doEvent = qtrue;
}
else if (animEvents[i].eventData[AED_SABER_SWING_PROBABILITY] > Q_irand(0, 99) )
{
doEvent = qtrue;
}
break;
case AEV_SABER_SPIN:
// Determine probability of playing sound
if (!animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY]) // 100%
{
doEvent = qtrue;
}
else if (animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY] > Q_irand(0, 99) )
{
doEvent = qtrue;
}
break;
case AEV_FOOTSTEP:
// Determine probability of playing sound
if (!animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY]) // 100%
{
doEvent = qtrue;
}
else if (animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY] > Q_irand(0, 99) )
{
doEvent = qtrue;
}
break;
case AEV_EFFECT:
// Determine probability of playing sound
if (!animEvents[i].eventData[AED_EFFECT_PROBABILITY]) // 100%
{
doEvent = qtrue;
}
else if (animEvents[i].eventData[AED_EFFECT_PROBABILITY] > Q_irand(0, 99) )
{
doEvent = qtrue;
}
break;
case AEV_FIRE:
// Determine probability of playing sound
if (!animEvents[i].eventData[AED_FIRE_PROBABILITY]) // 100%
{
doEvent = qtrue;
}
else if (animEvents[i].eventData[AED_FIRE_PROBABILITY] > Q_irand(0, 99) )
{
doEvent = qtrue;
}
break;
case AEV_MOVE:
doEvent = qtrue;
break;
default:
//doEvent = qfalse;//implicit
break;
}
// do event
if ( doEvent )
{
CG_PlayerAnimEventDo( &cg_entities[entNum], &animEvents[i] );
}
}
}
}
void CG_TriggerAnimSounds( centity_t *cent )
{ //this also sets the lerp frames, so I suggest you keep calling it regardless of if you want anim sounds.
int curFrame = 0;
float currentFrame = 0;
int sFileIndex;
assert(cent->localAnimIndex >= 0);
sFileIndex = cent->eventAnimIndex;
if (trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &currentFrame, cgs.gameModels, 0))
{
// the above may have failed, not sure what to do about it, current frame will be zero in that case
curFrame = floor( currentFrame );
}
if ( curFrame != cent->pe.legs.frame )
{
CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qfalse, cent->pe.legs.frame, curFrame, cent->currentState.number );
}
cent->pe.legs.oldFrame = cent->pe.torso.frame;
cent->pe.legs.frame = curFrame;
if (cent->noLumbar)
{ //probably a droid or something.
cent->pe.torso.oldFrame = cent->pe.legs.oldFrame;
cent->pe.torso.frame = cent->pe.legs.frame;
return;
}
if (trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &currentFrame, cgs.gameModels, 0))
{
curFrame = floor( currentFrame );
}
if ( curFrame != cent->pe.torso.frame )
{
CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qtrue, cent->pe.torso.frame, curFrame, cent->currentState.number );
}
cent->pe.torso.oldFrame = cent->pe.torso.frame;
cent->pe.torso.frame = curFrame;
cent->pe.torso.backlerp = 1.0f - (currentFrame - (float)curFrame);
}
static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale);
qboolean CG_InRoll( centity_t *cent )
{
switch ( (cent->currentState.legsAnim) )
{
case BOTH_GETUP_BROLL_B:
case BOTH_GETUP_BROLL_F:
case BOTH_GETUP_BROLL_L:
case BOTH_GETUP_BROLL_R:
case BOTH_GETUP_FROLL_B:
case BOTH_GETUP_FROLL_F:
case BOTH_GETUP_FROLL_L:
case BOTH_GETUP_FROLL_R:
case BOTH_ROLL_F:
case BOTH_ROLL_B:
case BOTH_ROLL_R:
case BOTH_ROLL_L:
if ( cent->pe.legs.animationTime > cg.time )
{
return qtrue;
}
break;
}
return qfalse;
}
qboolean CG_InRollAnim( centity_t *cent )
{
switch ( (cent->currentState.legsAnim) )
{
case BOTH_ROLL_F:
case BOTH_ROLL_B:
case BOTH_ROLL_R:
case BOTH_ROLL_L:
return qtrue;
}
return qfalse;
}
/*
===============
CG_SetLerpFrameAnimation
===============
*/
#include "../namespace_begin.h"
qboolean BG_SaberStanceAnim( int anim );
qboolean PM_RunningAnim( int anim );
#include "../namespace_end.h"
static void CG_SetLerpFrameAnimation( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float animSpeedMult, qboolean torsoOnly, qboolean flipState) {
animation_t *anim;
float animSpeed;
int flags=BONE_ANIM_OVERRIDE_FREEZE;
int oldAnim = -1;
int blendTime = 100;
float oldSpeed = lf->animationSpeed;
if (cent->localAnimIndex > 0)
{ //rockettroopers can't have broken arms, nor can anything else but humanoids
ci->brokenLimbs = cent->currentState.brokenLimbs;
}
oldAnim = lf->animationNumber;
lf->animationNumber = newAnimation;
if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) {
CG_Error( "Bad animation number: %i", newAnimation );
}
anim = &bgAllAnims[cent->localAnimIndex].anims[ newAnimation ];
lf->animation = anim;
lf->animationTime = lf->frameTime + abs(anim->frameLerp);
if (cent->localAnimIndex > 1 &&
anim->firstFrame == 0 &&
anim->numFrames == 0)
{ //We'll allow this for non-humanoids.
return;
}
if ( cg_debugAnim.integer && (cg_debugAnim.integer < 0 || cg_debugAnim.integer == cent->currentState.clientNum) ) {
if (lf == &cent->pe.legs)
{
CG_Printf( "%d: %d TORSO Anim: %i, '%s'\n", cg.time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation));
}
else
{
CG_Printf( "%d: %d LEGS Anim: %i, '%s'\n", cg.time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation));
}
}
if (cent->ghoul2)
{
qboolean resumeFrame = qfalse;
int beginFrame = -1;
int firstFrame;
int lastFrame;
#if 0 //disabled for now
float unused;
#endif
animSpeed = 50.0f / anim->frameLerp;
if (lf->animation->loopFrames != -1)
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (animSpeed < 0)
{
lastFrame = anim->firstFrame;
firstFrame = anim->firstFrame + anim->numFrames;
}
else
{
firstFrame = anim->firstFrame;
lastFrame = anim->firstFrame + anim->numFrames;
}
if (cg_animBlend.integer)
{
flags |= BONE_ANIM_BLEND;
}
if (BG_InDeathAnim(newAnimation))
{
flags &= ~BONE_ANIM_BLEND;
}
else if ( oldAnim != -1 &&
BG_InDeathAnim(oldAnim))
{
flags &= ~BONE_ANIM_BLEND;
}
if (flags & BONE_ANIM_BLEND)
{
if (BG_FlippingAnim(newAnimation))
{
blendTime = 200;
}
else if ( oldAnim != -1 &&
(BG_FlippingAnim(oldAnim)) )
{
blendTime = 200;
}
}
animSpeed *= animSpeedMult;
BG_SaberStartTransAnim(cent->currentState.number, cent->currentState.fireflag, cent->currentState.weapon, newAnimation, &animSpeed, cent->currentState.brokenLimbs);
if (torsoOnly)
{
if (lf->animationTorsoSpeed != animSpeedMult && newAnimation == oldAnim &&
flipState == lf->lastFlip)
{ //same animation, but changing speed, so we will want to resume off the frame we're on.
resumeFrame = qtrue;
}
lf->animationTorsoSpeed = animSpeedMult;
}
else
{
if (lf->animationSpeed != animSpeedMult && newAnimation == oldAnim &&
flipState == lf->lastFlip)
{ //same animation, but changing speed, so we will want to resume off the frame we're on.
resumeFrame = qtrue;
}
lf->animationSpeed = animSpeedMult;
}
//vehicles may have torso etc but we only want to animate the root bone
if ( cent->currentState.NPC_class == CLASS_VEHICLE )
{
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed,cg.time, beginFrame, blendTime);
return;
}
if (torsoOnly && !cent->noLumbar)
{ //rww - The guesswork based on the lerp frame figures is usually BS, so I've resorted to a call to get the frame of the bone directly.
float GBAcFrame = 0;
if (resumeFrame)
{ //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame.
trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &GBAcFrame, NULL, 0);
beginFrame = GBAcFrame;
}
//even if resuming, also be sure to check if we are running the same frame on the legs. If so, we want to use their frame no matter what.
trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &GBAcFrame, NULL, 0);
if ((cent->currentState.torsoAnim) == (cent->currentState.legsAnim) && GBAcFrame >= anim->firstFrame && GBAcFrame <= (anim->firstFrame + anim->numFrames))
{ //if the legs are already running this anim, pick up on the exact same frame to avoid the "wobbly spine" problem.
beginFrame = GBAcFrame;
}
if (firstFrame > lastFrame || ci->torsoAnim == newAnimation)
{ //don't resume on backwards playing animations.. I guess.
beginFrame = -1;
}
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, flags, animSpeed,cg.time, beginFrame, blendTime);
// Update the torso frame with the new animation
cent->pe.torso.frame = firstFrame;
if (ci)
{
ci->torsoAnim = newAnimation;
}
}
else
{
if (resumeFrame)
{ //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame.
float GBAcFrame = 0;
trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &GBAcFrame, NULL, 0);
beginFrame = GBAcFrame;
}
if ((beginFrame < firstFrame) || (beginFrame > lastFrame))
{ //out of range, don't use it then.
beginFrame = -1;
}
if (cent->currentState.torsoAnim == cent->currentState.legsAnim &&
(ci->legsAnim != newAnimation || oldSpeed != animSpeed))
{ //alright, we are starting an anim on the legs, and that same anim is already playing on the toro, so pick up the frame.
float GBAcFrame = 0;
int oldBeginFrame = beginFrame;
trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &GBAcFrame, NULL, 0);
beginFrame = GBAcFrame;
if ((beginFrame < firstFrame) || (beginFrame > lastFrame))
{ //out of range, don't use it then.
beginFrame = oldBeginFrame;
}
}
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime);
if (ci)
{
ci->legsAnim = newAnimation;
}
}
if (cent->localAnimIndex <= 1 && (cent->currentState.torsoAnim) == newAnimation && !cent->noLumbar)
{ //make sure we're humanoid before we access the motion bone
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime);
}
#if 0 //disabled for now
if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs &&
(cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM)))
{ //broken left arm
char *brokenBone = "lhumerus";
animation_t *armAnim;
int armFirstFrame;
int armLastFrame;
int armFlags = 0;
float armAnimSpeed;
armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_DEAD21 ];
ci->brokenLimbs = cent->currentState.brokenLimbs;
armFirstFrame = armAnim->firstFrame;
armLastFrame = armAnim->firstFrame+armAnim->numFrames;
armAnimSpeed = 50.0f / armAnim->frameLerp;
armFlags = BONE_ANIM_OVERRIDE_LOOP;
if (cg_animBlend.integer)
{
armFlags |= BONE_ANIM_BLEND;
}
trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, blendTime);
}
else if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs &&
(cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM)))
{ //broken right arm
char *brokenBone = "rhumerus";
char *supportBone = "lhumerus";
ci->brokenLimbs = cent->currentState.brokenLimbs;
//Only put the arm in a broken pose if the anim is such that we
//want to allow it.
if ((//cent->currentState.weapon == WP_MELEE ||
cent->currentState.weapon != WP_SABER ||
BG_SaberStanceAnim(newAnimation) ||
PM_RunningAnim(newAnimation)) &&
cent->currentState.torsoAnim == newAnimation &&
(!ci->saber[1].model[0] || cent->currentState.weapon != WP_SABER))
{
int armFirstFrame;
int armLastFrame;
int armFlags = 0;
float armAnimSpeed;
animation_t *armAnim;
if (cent->currentState.weapon == WP_MELEE ||
cent->currentState.weapon == WP_SABER ||
cent->currentState.weapon == WP_BRYAR_PISTOL)
{ //don't affect this arm if holding a gun, just make the other arm support it
armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_ATTACK2 ];
//armFirstFrame = armAnim->firstFrame;
armFirstFrame = armAnim->firstFrame+armAnim->numFrames;
armLastFrame = armAnim->firstFrame+armAnim->numFrames;
armAnimSpeed = 50.0f / armAnim->frameLerp;
armFlags = BONE_ANIM_OVERRIDE_LOOP;
/*
if (cg_animBlend.integer)
{
armFlags |= BONE_ANIM_BLEND;
}
*/
//No blend on the broken arm
trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, 0);
}
else
{ //we want to keep the broken bone updated for some cases
trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime);
}
if (newAnimation != BOTH_MELEE1 &&
newAnimation != BOTH_MELEE2 &&
(newAnimation == TORSO_WEAPONREADY2 || newAnimation == BOTH_ATTACK2 || cent->currentState.weapon < WP_BRYAR_PISTOL))
{
//Now set the left arm to "support" the right one
armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_STAND2 ];
armFirstFrame = armAnim->firstFrame;
armLastFrame = armAnim->firstFrame+armAnim->numFrames;
armAnimSpeed = 50.0f / armAnim->frameLerp;
armFlags = BONE_ANIM_OVERRIDE_LOOP;
if (cg_animBlend.integer)
{
armFlags |= BONE_ANIM_BLEND;
}
trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, 150);
}
else
{ //we want to keep the support bone updated for some cases
trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime);
}
}
else if (cent->currentState.torsoAnim == newAnimation)
{ //otherwise, keep it set to the same as the torso
trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime);
}
}
else if (ci &&
(ci->brokenLimbs ||
trap_G2API_GetBoneFrame(cent->ghoul2, "lhumerus", cg.time, &unused, cgs.gameModels, 0) ||
trap_G2API_GetBoneFrame(cent->ghoul2, "rhumerus", cg.time, &unused, cgs.gameModels, 0)))
//rwwFIXMEFIXME: brokenLimbs gets stomped sometimes, but it shouldn't.
{ //remove the bone now so it can be set again
char *brokenBone = NULL;
int broken = 0;
//Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index
//(well, in theory, I haven't actually run into the problem)
if (ci->brokenLimbs & (1<<BROKENLIMB_LARM))
{
brokenBone = "lhumerus";
broken |= (1<<BROKENLIMB_LARM);
}
else if (ci->brokenLimbs & (1<<BROKENLIMB_RARM))
{ //can only have one arm broken at once.
brokenBone = "rhumerus";
broken |= (1<<BROKENLIMB_RARM);
//want to remove the support bone too then
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg.time, -1, 0);
if (!trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0))
{
assert(0);
Com_Printf("WARNING: Failed to remove lhumerus\n");
}
}
if (!brokenBone)
{
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg.time, -1, 0);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "rhumerus", 0, 1, 0, 0, cg.time, -1, 0);
trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0);
trap_G2API_RemoveBone(cent->ghoul2, "rhumerus", 0);
ci->brokenLimbs = 0;
}
else
{
//Set the flags and stuff to 0, so that the remove will succeed
trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, 0, 1, 0, 0, cg.time, -1, 0);
//Now remove it
if (!trap_G2API_RemoveBone(cent->ghoul2, brokenBone, 0))
{
assert(0);
Com_Printf("WARNING: Failed to remove %s\n", brokenBone);
}
ci->brokenLimbs &= ~broken;
}
}
#endif
}
}
/*
===============
CG_FirstAnimFrame
Returns true if the lerpframe is on its first frame of animation.
Otherwise false.
This is used to scale an animation into higher-speed without restarting
the animation before it completes at normal speed, in the case of a looping
animation (such as the leg running anim).
===============
*/
static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale)
{
if (torsoOnly)
{
if (lf->animationTorsoSpeed == speedScale)
{
return qfalse;
}
}
else
{
if (lf->animationSpeed == speedScale)
{
return qfalse;
}
}
//I don't care where it is in the anim now, I am going to pick up from the same bone frame.
/*
if (lf->animation->numFrames < 2)
{
return qtrue;
}
if (lf->animation->firstFrame == lf->frame)
{
return qtrue;
}
*/
return qtrue;
}
/*
===============
CG_RunLerpFrame
Sets cg.snap, cg.oldFrame, and cg.backlerp
cg.time should be between oldFrameTime and frameTime after exit
===============
*/
static void CG_RunLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, qboolean flipState, int newAnimation, float speedScale, qboolean torsoOnly)
{
// debugging tool to get no animations
if ( cg_animSpeed.integer == 0 ) {
lf->oldFrame = lf->frame = lf->backlerp = 0;
return;
}
// see if the animation sequence is switching
if (cent->currentState.forceFrame)
{
if (lf->lastForcedFrame != cent->currentState.forceFrame)
{
int flags = BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND;
float animSpeed = 1.0f;
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150);
}
lf->lastForcedFrame = cent->currentState.forceFrame;
lf->animationNumber = 0;
}
else
{
lf->lastForcedFrame = -1;
if ( (newAnimation != lf->animationNumber || cent->currentState.brokenLimbs != ci->brokenLimbs || lf->lastFlip != flipState || !lf->animation) || (CG_FirstAnimFrame(lf, torsoOnly, speedScale)) )
{
CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation, speedScale, torsoOnly, flipState);
}
}
lf->lastFlip = flipState;
if ( lf->frameTime > cg.time + 200 ) {
lf->frameTime = cg.time;
}
if ( lf->oldFrameTime > cg.time ) {
lf->oldFrameTime = cg.time;
}
// calculate current lerp value
if ( lf->frameTime == lf->oldFrameTime ) {
lf->backlerp = 0;
} else {
lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
}
}
/*
===============
CG_ClearLerpFrame
===============
*/
static void CG_ClearLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int animationNumber, qboolean torsoOnly) {
lf->frameTime = lf->oldFrameTime = cg.time;
CG_SetLerpFrameAnimation( cent, ci, lf, animationNumber, 1, torsoOnly, qfalse );
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
===============
*/
#include "../namespace_begin.h"
qboolean PM_WalkingAnim( int anim );
#include "../namespace_end.h"
static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
int *torsoOld, int *torso, float *torsoBackLerp ) {
clientInfo_t *ci;
int clientNum;
float speedScale;
clientNum = cent->currentState.clientNum;
if ( cg_noPlayerAnims.integer ) {
*legsOld = *legs = *torsoOld = *torso = 0;
return;
}
if (!PM_RunningAnim(cent->currentState.legsAnim) &&
!PM_WalkingAnim(cent->currentState.legsAnim))
{ //if legs are not in a walking/running anim then just animate at standard speed
speedScale = 1.0f;
}
else if (cent->currentState.forcePowersActive & (1 << FP_RAGE))
{
speedScale = 1.3f;
}
else if (cent->currentState.forcePowersActive & (1 << FP_SPEED))
{
speedScale = 1.7f;
}
else
{
speedScale = 1.0f;
}
if (cent->currentState.eType == ET_NPC)
{
ci = cent->npcClient;
assert(ci);
}
else
{
ci = &cgs.clientinfo[ clientNum ];
}
CG_RunLerpFrame( cent, ci, &cent->pe.legs, cent->currentState.legsFlip, cent->currentState.legsAnim, speedScale, qfalse);
if (!(cent->currentState.forcePowersActive & (1 << FP_RAGE)))
{ //don't affect torso anim speed unless raged
speedScale = 1.0f;
}
else
{
speedScale = 1.7f;
}
*legsOld = cent->pe.legs.oldFrame;
*legs = cent->pe.legs.frame;
*legsBackLerp = cent->pe.legs.backlerp;
// If this is not a vehicle, you may lerm the frame (since vehicles never have a torso anim). -AReis
if ( cent->currentState.NPC_class != CLASS_VEHICLE )
{
CG_RunLerpFrame( cent, ci, &cent->pe.torso, cent->currentState.torsoFlip, cent->currentState.torsoAnim, speedScale, qtrue );
*torsoOld = cent->pe.torso.oldFrame;
*torso = cent->pe.torso.frame;
*torsoBackLerp = cent->pe.torso.backlerp;
}
}
/*
=============================================================================
PLAYER ANGLES
=============================================================================
*/
#if 0
typedef struct boneAngleParms_s {
void *ghoul2;
int modelIndex;
char *boneName;
vec3_t angles;
int flags;
int up;
int right;
int forward;
qhandle_t *modelList;
int blendTime;
int currentTime;
qboolean refreshSet;
} boneAngleParms_t;
boneAngleParms_t cgBoneAnglePostSet;
#endif
void CG_G2SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags,
const int up, const int right, const int forward, qhandle_t *modelList,
int blendTime , int currentTime )
{ //we want to hold off on setting the bone angles until the end of the frame, because every time we set
//them the entire skeleton has to be reconstructed.
#if 0
//This function should ONLY be called from CG_Player() or a function that is called only within CG_Player().
//At the end of the frame we will check to use this information to call SetBoneAngles
memset(&cgBoneAnglePostSet, 0, sizeof(cgBoneAnglePostSet));
cgBoneAnglePostSet.ghoul2 = ghoul2;
cgBoneAnglePostSet.modelIndex = modelIndex;
cgBoneAnglePostSet.boneName = (char *)boneName;
cgBoneAnglePostSet.angles[0] = angles[0];
cgBoneAnglePostSet.angles[1] = angles[1];
cgBoneAnglePostSet.angles[2] = angles[2];
cgBoneAnglePostSet.flags = flags;
cgBoneAnglePostSet.up = up;
cgBoneAnglePostSet.right = right;
cgBoneAnglePostSet.forward = forward;
cgBoneAnglePostSet.modelList = modelList;
cgBoneAnglePostSet.blendTime = blendTime;
cgBoneAnglePostSet.currentTime = currentTime;
cgBoneAnglePostSet.refreshSet = qtrue;
#endif
//We don't want to go with the delayed approach, we want out bolt points and everything to be updated in realtime.
//We'll just take the reconstructs and live with them.
trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList,
blendTime, currentTime);
}
/*
================
CG_Rag_Trace
Variant on CG_Trace. Doesn't trace for ents because ragdoll engine trace code has no entity
trace access. Maybe correct this sometime, so bmodel col. at least works with ragdoll.
But I don't want to slow it down..
================
*/
void CG_Rag_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end,
int skipNumber, int mask ) {
trap_CM_BoxTrace ( result, start, end, mins, maxs, 0, mask);
result->entityNum = result->fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
}
//#define _RAG_BOLT_TESTING
#ifdef _RAG_BOLT_TESTING
void CG_TempTestFunction(centity_t *cent, vec3_t forcedAngles)
{
mdxaBone_t boltMatrix;
vec3_t tAngles;
vec3_t bOrg;
vec3_t bDir;
vec3_t uOrg;
VectorSet(tAngles, 0, cent->lerpAngles[YAW], 0);
trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, tAngles, cent->lerpOrigin,
cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, bOrg);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, bDir);
VectorMA(bOrg, 40, bDir, uOrg);
CG_TestLine(bOrg, uOrg, 50, 0x0000ff, 1);
cent->turAngles[YAW] = forcedAngles[YAW];
}
#endif
//list of valid ragdoll effectors
static const char *cg_effectorStringTable[] =
{ //commented out the ones I don't want dragging to affect
// "thoracic",
// "rhand",
"lhand",
"rtibia",
"ltibia",
"rtalus",
"ltalus",
// "rradiusX",
"lradiusX",
"rfemurX",
"lfemurX",
// "ceyebrow",
NULL //always terminate
};
//we want to see which way the pelvis is facing to get a relatively oriented base settling frame
//this is to avoid the arms stretching in opposite directions on the body trying to reach the base
//pose if the pelvis is flipped opposite of the base pose or something -rww
static int CG_RagAnimForPositioning(centity_t *cent)
{
int bolt;
vec3_t dir;
mdxaBone_t matrix;
assert(cent->ghoul2);
bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis");
assert(bolt > -1);
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->turAngles, cent->lerpOrigin,
cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Z, dir);
if (dir[2] > 0.0f)
{ //facing up
return BOTH_DEADFLOP2;
}
else
{ //facing down
return BOTH_DEADFLOP1;
}
}
//rww - cgame interface for the ragdoll stuff.
//Returns qtrue if the entity is now in a ragdoll state, otherwise qfalse.
qboolean CG_RagDoll(centity_t *cent, vec3_t forcedAngles)
{
vec3_t usedOrg;
qboolean inSomething = qfalse;
int ragAnim;//BOTH_DEAD1; //BOTH_DEATH1;
if (!cg_ragDoll.integer)
{
return qfalse;
}
if (cent->localAnimIndex)
{ //don't rag non-humanoids
return qfalse;
}
VectorCopy(cent->lerpOrigin, usedOrg);
if (!cent->isRagging)
{ //If we're not in a ragdoll state, perform the checks.
if (cent->currentState.eFlags & EF_RAG)
{ //want to go into it no matter what then
inSomething = qtrue;
}
else if (cent->currentState.groundEntityNum == ENTITYNUM_NONE)
{
vec3_t cVel;
VectorCopy(cent->currentState.pos.trDelta, cVel);
if (VectorNormalize(cVel) > 400)
{ //if he's flying through the air at a good enough speed, switch into ragdoll
inSomething = qtrue;
}
}
if (cent->currentState.eType == ET_BODY)
{ //just rag bodies immediately if their own was ragging on respawn
if (cent->ownerRagging)
{
cent->isRagging = qtrue;
return qfalse;
}
}
if (cg_ragDoll.integer > 1)
{
inSomething = qtrue;
}
if (!inSomething)
{
int anim = (cent->currentState.legsAnim);
int dur = (bgAllAnims[cent->localAnimIndex].anims[anim].numFrames-1) * fabs((float)(bgAllAnims[cent->localAnimIndex].anims[anim].frameLerp));
int i = 0;
int boltChecks[5];
vec3_t boltPoints[5];
vec3_t trStart, trEnd;
vec3_t tAng;
qboolean deathDone = qfalse;
trace_t tr;
mdxaBone_t boltMatrix;
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
if (cent->pe.legs.animationTime > 50 && (cg.time - cent->pe.legs.animationTime) > dur)
{ //Looks like the death anim is done playing
deathDone = qtrue;
}
if (deathDone)
{ //only trace from the hands if the death anim is already done.
boltChecks[0] = trap_G2API_AddBolt(cent->ghoul2, 0, "rhand");
boltChecks[1] = trap_G2API_AddBolt(cent->ghoul2, 0, "lhand");
}
else
{ //otherwise start the trace loop at the cranium.
i = 2;
}
boltChecks[2] = trap_G2API_AddBolt(cent->ghoul2, 0, "cranium");
//boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtarsal");
//boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltarsal");
boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtalus");
boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltalus");
//This may seem bad, but since we have a bone cache now it should manage to not be too disgustingly slow.
//Do the head first, because the hands reference it anyway.
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[2], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[2]);
while (i < 5)
{
if (i < 2)
{ //when doing hands, trace to the head instead of origin
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]);
VectorCopy(boltPoints[i], trStart);
VectorCopy(boltPoints[2], trEnd);
}
else
{
if (i > 2)
{ //2 is the head, which already has the bolt point.
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]);
}
VectorCopy(boltPoints[i], trStart);
VectorCopy(cent->lerpOrigin, trEnd);
}
//Now that we have all that sorted out, trace between the two points we desire.
CG_Rag_Trace(&tr, trStart, NULL, NULL, trEnd, cent->currentState.number, MASK_SOLID);
if (tr.fraction != 1.0 || tr.startsolid || tr.allsolid)
{ //Hit something or start in solid, so flag it and break.
//This is a slight hack, but if we aren't done with the death anim, we don't really want to
//go into ragdoll unless our body has a relatively "flat" pitch.
#if 0
vec3_t vSub;
//Check the pitch from the head to the right foot (should be reasonable)
VectorSubtract(boltPoints[2], boltPoints[3], vSub);
VectorNormalize(vSub);
vectoangles(vSub, vSub);
if (deathDone || (vSub[PITCH] < 50 && vSub[PITCH] > -50))
{
inSomething = qtrue;
}
#else
inSomething = qtrue;
#endif
break;
}
i++;
}
}
if (inSomething)
{
cent->isRagging = qtrue;
#if 0
VectorClear(cent->lerpOriginOffset);
#endif
}
}
if (cent->isRagging)
{ //We're in a ragdoll state, so make the call to keep our positions updated and whatnot.
sharedRagDollParams_t tParms;
sharedRagDollUpdateParams_t tuParms;
ragAnim = CG_RagAnimForPositioning(cent);
if (cent->ikStatus)
{ //ik must be reset before ragdoll is started, or you'll get some interesting results.
trap_G2API_SetBoneIKState(cent->ghoul2, cg.time, NULL, IKS_NONE, NULL);
cent->ikStatus = qfalse;
}
//these will be used as "base" frames for the ragoll settling.
tParms.startFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame;// + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames;
tParms.endFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames;
#if 0
{
float animSpeed = 0;
int blendTime = 600;
int flags = 0;//BONE_ANIM_OVERRIDE_FREEZE;
if (bgAllAnims[cent->localAnimIndex].anims[ragAnim].loopFrames != -1)
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
/*
if (cg_animBlend.integer)
{
flags |= BONE_ANIM_BLEND;
}
*/
animSpeed = 50.0f / bgAllAnims[cent->localAnimIndex].anims[ragAnim].frameLerp;
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", tParms.startFrame, tParms.endFrame, flags, animSpeed,cg.time, -1, blendTime);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg.time, -1, blendTime);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg.time, -1, blendTime);
}
#elif 1 //with my new method of doing things I want it to continue the anim
{
float currentFrame;
int startFrame, endFrame;
int flags;
float animSpeed;
if (trap_G2API_GetBoneAnim(cent->ghoul2, "model_root", cg.time, &currentFrame, &startFrame, &endFrame, &flags, &animSpeed, cgs.gameModels, 0))
{ //lock the anim on the current frame.
int blendTime = 500;
animation_t *curAnim = &bgAllAnims[cent->localAnimIndex].anims[cent->currentState.legsAnim];
if (currentFrame >= (curAnim->firstFrame + curAnim->numFrames-1))
{ //this is sort of silly but it works for now.
currentFrame = (curAnim->firstFrame + curAnim->numFrames-2);
}
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", currentFrame, currentFrame+1, flags, animSpeed,cg.time, currentFrame, blendTime);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", currentFrame, currentFrame+1, flags, animSpeed, cg.time, currentFrame, blendTime);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", currentFrame, currentFrame+1, flags, animSpeed, cg.time, currentFrame, blendTime);
}
}
#endif
CG_G2SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
CG_G2SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
CG_G2SetBoneAngles(cent->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
CG_G2SetBoneAngles(cent->ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
VectorCopy(forcedAngles, tParms.angles);
VectorCopy(usedOrg, tParms.position);
VectorCopy(cent->modelScale, tParms.scale);
tParms.me = cent->currentState.number;
tParms.collisionType = 1;
tParms.RagPhase = RP_DEATH_COLLISION;
tParms.fShotStrength = 4;
trap_G2API_SetRagDoll(cent->ghoul2, &tParms);
VectorCopy(forcedAngles, tuParms.angles);
VectorCopy(usedOrg, tuParms.position);
VectorCopy(cent->modelScale, tuParms.scale);
tuParms.me = cent->currentState.number;
tuParms.settleFrame = tParms.endFrame-1;
if (cent->currentState.groundEntityNum != ENTITYNUM_NONE)
{
VectorClear(tuParms.velocity);
}
else
{
VectorScale(cent->currentState.pos.trDelta, 2.0f, tuParms.velocity);
}
trap_G2API_AnimateG2Models(cent->ghoul2, cg.time, &tuParms);
//So if we try to get a bolt point it's still correct
cent->turAngles[YAW] =
cent->lerpAngles[YAW] =
cent->pe.torso.yawAngle =
cent->pe.legs.yawAngle = forcedAngles[YAW];
if (cent->currentState.ragAttach &&
(cent->currentState.eType != ET_NPC || cent->currentState.NPC_class != CLASS_VEHICLE))
{
centity_t *grabEnt;
if (cent->currentState.ragAttach == ENTITYNUM_NONE)
{ //switch cl 0 and entitynum_none, so we can operate on the "if non-0" concept
grabEnt = &cg_entities[0];
}
else
{
grabEnt = &cg_entities[cent->currentState.ragAttach];
}
if (grabEnt->ghoul2)
{
mdxaBone_t matrix;
vec3_t bOrg;
vec3_t thisHand;
vec3_t hands;
vec3_t pcjMin, pcjMax;
vec3_t pDif;
vec3_t thorPoint;
float difLen;
int thorBolt;
//Get the person who is holding our hand's hand location
trap_G2API_GetBoltMatrix(grabEnt->ghoul2, 0, 0, &matrix, grabEnt->turAngles, grabEnt->lerpOrigin,
cg.time, cgs.gameModels, grabEnt->modelScale);
BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bOrg);
//Get our hand's location
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, 0, &matrix, cent->turAngles, cent->lerpOrigin,
cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thisHand);
//Get the position of the thoracic bone for hinting its velocity later on
thorBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "thoracic");
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, thorBolt, &matrix, cent->turAngles, cent->lerpOrigin,
cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thorPoint);
VectorSubtract(bOrg, thisHand, hands);
if (VectorLength(hands) < 3.0f)
{
trap_G2API_RagForceSolve(cent->ghoul2, qfalse);
}
else
{
trap_G2API_RagForceSolve(cent->ghoul2, qtrue);
}
//got the hand pos of him, now we want to make our hand go to it
trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", bOrg);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", bOrg);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", bOrg);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", bOrg);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", bOrg);
//Make these two solve quickly so we can update decently
trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 1.5f);
trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 1.5f);
//Break the constraints on them I suppose
VectorSet(pcjMin, -999, -999, -999);
VectorSet(pcjMax, 999, 999, 999);
trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax);
trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax);
cent->overridingBones = cg.time + 2000;
//hit the thoracic velocity to the hand point
VectorSubtract(bOrg, thorPoint, hands);
VectorNormalize(hands);
VectorScale(hands, 2048.0f, hands);
trap_G2API_RagEffectorKick(cent->ghoul2, "thoracic", hands);
trap_G2API_RagEffectorKick(cent->ghoul2, "ceyebrow", hands);
VectorSubtract(cent->ragLastOrigin, cent->lerpOrigin, pDif);
VectorCopy(cent->lerpOrigin, cent->ragLastOrigin);
if (cent->ragLastOriginTime >= cg.time && cent->currentState.groundEntityNum != ENTITYNUM_NONE)
{ //make sure it's reasonably updated
difLen = VectorLength(pDif);
if (difLen > 0.0f)
{ //if we're being dragged, then kick all the bones around a bit
vec3_t dVel;
vec3_t rVel;
int i = 0;
if (difLen < 12.0f)
{
VectorScale(pDif, 12.0f/difLen, pDif);
difLen = 12.0f;
}
while (cg_effectorStringTable[i])
{
VectorCopy(pDif, dVel);
dVel[2] = 0;
//Factor in a random velocity
VectorSet(rVel, flrand(-0.1f, 0.1f), flrand(-0.1f, 0.1f), flrand(0.1f, 0.5));
VectorScale(rVel, 8.0f, rVel);
VectorAdd(dVel, rVel, dVel);
VectorScale(dVel, 10.0f, dVel);
trap_G2API_RagEffectorKick(cent->ghoul2, cg_effectorStringTable[i], dVel);
#if 0
{
mdxaBone_t bm;
vec3_t borg;
vec3_t vorg;
int b = trap_G2API_AddBolt(cent->ghoul2, 0, cg_effectorStringTable[i]);
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &bm, cent->turAngles, cent->lerpOrigin, cg.time,
cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&bm, ORIGIN, borg);
VectorMA(borg, 1.0f, dVel, vorg);
CG_TestLine(borg, vorg, 50, 0x0000ff, 1);
}
#endif
i++;
}
}
}
cent->ragLastOriginTime = cg.time + 1000;
}
}
else if (cent->overridingBones)
{ //reset things to their normal rag state
vec3_t pcjMin, pcjMax;
vec3_t dVel;
//got the hand pos of him, now we want to make our hand go to it
trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", NULL);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", NULL);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", NULL);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", NULL);
trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", NULL);
VectorSet(dVel, 0.0f, 0.0f, -64.0f);
trap_G2API_RagEffectorKick(cent->ghoul2, "rhand", dVel);
trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 0.0f);
trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 0.0f);
VectorSet(pcjMin,-100.0f,-40.0f,-15.0f);
VectorSet(pcjMax,-15.0f,80.0f,15.0f);
trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax);
VectorSet(pcjMin,-25.0f,-20.0f,-20.0f);
VectorSet(pcjMax,90.0f,20.0f,-20.0f);
trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax);
if (cent->overridingBones < cg.time)
{
trap_G2API_RagForceSolve(cent->ghoul2, qfalse);
cent->overridingBones = 0;
}
else
{
trap_G2API_RagForceSolve(cent->ghoul2, qtrue);
}
}
return qtrue;
}
return qfalse;
}
//set the bone angles of this client entity based on data from the server -rww
void CG_G2ServerBoneAngles(centity_t *cent)
{
int i = 0;
int bone = cent->currentState.boneIndex1;
int flags, up, right, forward;
vec3_t boneAngles;
VectorCopy(cent->currentState.boneAngles1, boneAngles);
while (i < 4)
{ //cycle through the 4 bone index values on the entstate
if (bone)
{ //if it's non-0 then it could have something in it.
const char *boneName = CG_ConfigString(CS_G2BONES+bone);
if (boneName && boneName[0])
{ //got the bone, now set the angles from the corresponding entitystate boneangles value.
flags = BONE_ANGLES_POSTMULT;
//get the orientation out of our bit field
forward = (cent->currentState.boneOrient)&7; //3 bits from bit 0
right = (cent->currentState.boneOrient>>3)&7; //3 bits from bit 3
up = (cent->currentState.boneOrient>>6)&7; //3 bits from bit 6
trap_G2API_SetBoneAngles(cent->ghoul2, 0, boneName, boneAngles, flags, up, right, forward, cgs.gameModels, 100, cg.time);
}
}
switch (i)
{
case 0:
bone = cent->currentState.boneIndex2;
VectorCopy(cent->currentState.boneAngles2, boneAngles);
break;
case 1:
bone = cent->currentState.boneIndex3;
VectorCopy(cent->currentState.boneAngles3, boneAngles);
break;
case 2:
bone = cent->currentState.boneIndex4;
VectorCopy(cent->currentState.boneAngles4, boneAngles);
break;
default:
break;
}
i++;
}
}
/*
-------------------------
CG_G2SetHeadBlink
-------------------------
*/
static void CG_G2SetHeadBlink( centity_t *cent, qboolean bStart )
{
vec3_t desiredAngles;
int blendTime = 80;
qboolean bWink = qfalse;
const int hReye = trap_G2API_AddBolt( cent->ghoul2, 0, "reye" );
const int hLeye = trap_G2API_AddBolt( cent->ghoul2, 0, "leye" );
if (hLeye == -1)
{
return;
}
VectorClear(desiredAngles);
if (bStart)
{
desiredAngles[YAW] = -50;
if ( random() > 0.95f )
{
bWink = qtrue;
blendTime /=3;
}
}
trap_G2API_SetBoneAngles( cent->ghoul2, 0, "leye", desiredAngles,
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time );
if (hReye == -1)
{
return;
}
if (!bWink)
{
trap_G2API_SetBoneAngles( cent->ghoul2, 0, "reye", desiredAngles,
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time );
}
}
/*
-------------------------
CG_G2SetHeadAnims
-------------------------
*/
static void CG_G2SetHeadAnim( centity_t *cent, int anim )
{
const int blendTime = 50;
const animation_t *animations = bgAllAnims[cent->localAnimIndex].anims;
int animFlags = BONE_ANIM_OVERRIDE ;//| BONE_ANIM_BLEND;
// animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps).
// float timeScaleMod = (cg_timescale.value&&gent&&gent->s.clientNum==0&&!player_locked&&!MatrixMode&&gent->client->ps.forcePowersActive&(1<<FP_SPEED))?(1.0/cg_timescale.value):1.0;
const float timeScaleMod = (cg_timescale.value)?(1.0/cg_timescale.value):1.0;
float animSpeed = 50.0f / animations[anim].frameLerp * timeScaleMod;
int firstFrame;
int lastFrame;
if (animations[anim].numFrames <= 0)
{
return;
}
if (anim == FACE_DEAD)
{
animFlags |= BONE_ANIM_OVERRIDE_FREEZE;
}
// animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps).
if ( animSpeed < 0 )
{//play anim backwards
lastFrame = animations[anim].firstFrame -1;
firstFrame = (animations[anim].numFrames -1) + animations[anim].firstFrame ;
}
else
{
firstFrame = animations[anim].firstFrame;
lastFrame = (animations[anim].numFrames) + animations[anim].firstFrame;
}
// first decide if we are doing an animation on the head already
// int startFrame, endFrame;
// const qboolean animatingHead = gi.G2API_GetAnimRangeIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone, &startFrame, &endFrame);
// if (!animatingHead || ( animations[anim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again
{
// gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone,
// firstFrame, lastFrame, animFlags, animSpeed, cg.time, -1, blendTime);
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "face", firstFrame, lastFrame, animFlags, animSpeed,
cg.time, -1, blendTime);
}
}
qboolean CG_G2PlayerHeadAnims( centity_t *cent )
{
clientInfo_t *ci = NULL;
int anim = -1;
int voiceVolume = 0;
if(cent->localAnimIndex > 1)
{ //only do this for humanoids
return qfalse;
}
if (cent->noFace)
{ // i don't have a face
return qfalse;
}
if (cent->currentState.number < MAX_CLIENTS)
{
ci = &cgs.clientinfo[cent->currentState.number];
}
else
{
ci = cent->npcClient;
}
if (!ci)
{
return qfalse;
}
if ( cent->currentState.eFlags & EF_DEAD )
{//Dead people close their eyes and don't make faces!
anim = FACE_DEAD;
ci->facial_blink = -1;
}
else
{
if (!ci->facial_blink)
{ // set the timers
ci->facial_blink = cg.time + flrand(4000.0, 8000.0);
ci->facial_frown = cg.time + flrand(6000.0, 10000.0);
ci->facial_aux = cg.time + flrand(6000.0, 10000.0);
}
//are we blinking?
if (ci->facial_blink < 0)
{ // yes, check if we are we done blinking ?
if (-(ci->facial_blink) < cg.time)
{ // yes, so reset blink timer
ci->facial_blink = cg.time + flrand(4000.0, 8000.0);
CG_G2SetHeadBlink( cent, qfalse ); //stop the blink
}
}
else // no we aren't blinking
{
if (ci->facial_blink < cg.time)// but should we start ?
{
CG_G2SetHeadBlink( cent, qtrue );
if (ci->facial_blink == 1)
{//requested to stay shut by SET_FACEEYESCLOSED
ci->facial_blink = -(cg.time + 99999999.0f);// set blink timer
}
else
{
ci->facial_blink = -(cg.time + 300.0f);// set blink timer
}
}
}
voiceVolume = trap_S_GetVoiceVolume(cent->currentState.number);
if (voiceVolume > 0) // if we aren't talking, then it will be 0, -1 for talking but paused
{
anim = FACE_TALK1 + voiceVolume -1;
}
else if (voiceVolume == 0) //don't do aux if in a slient part of speech
{//not talking
if (ci->facial_aux < 0) // are we auxing ?
{ //yes
if (-(ci->facial_aux) < cg.time)// are we done auxing ?
{ // yes, reset aux timer
ci->facial_aux = cg.time + flrand(7000.0, 10000.0);
}
else
{ // not yet, so choose aux
anim = FACE_ALERT;
}
}
else // no we aren't auxing
{ // but should we start ?
if (ci->facial_aux < cg.time)
{//yes
anim = FACE_ALERT;
// set aux timer
ci->facial_aux = -(cg.time + 2000.0);
}
}
if (anim != -1) //we we are auxing, see if we should override with a frown
{
if (ci->facial_frown < 0)// are we frowning ?
{ // yes,
if (-(ci->facial_frown) < cg.time)//are we done frowning ?
{ // yes, reset frown timer
ci->facial_frown = cg.time + flrand(7000.0, 10000.0);
}
else
{ // not yet, so choose frown
anim = FACE_FROWN;
}
}
else// no we aren't frowning
{ // but should we start ?
if (ci->facial_frown < cg.time)
{
anim = FACE_FROWN;
// set frown timer
ci->facial_frown = -(cg.time + 2000.0);
}
}
}
}//talking
}//dead
if (anim != -1)
{
CG_G2SetHeadAnim( cent, anim );
return qtrue;
}
return qfalse;
}
static void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t legsAngles)
{
clientInfo_t *ci;
//rww - now do ragdoll stuff
if ((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG))
{
vec3_t forcedAngles;
VectorClear(forcedAngles);
forcedAngles[YAW] = cent->lerpAngles[YAW];
if (CG_RagDoll(cent, forcedAngles))
{ //if we managed to go into the rag state, give our ent axis the forced angles and return.
AnglesToAxis( forcedAngles, legs );
VectorCopy(forcedAngles, legsAngles);
return;
}
}
else if (cent->isRagging)
{
cent->isRagging = qfalse;
trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll.
}
if (cent->currentState.eType == ET_NPC)
{
ci = cent->npcClient;
assert(ci);
}
else
{
ci = &cgs.clientinfo[cent->currentState.number];
}
//rww - Quite possibly the most arguments for a function ever.
if (cent->localAnimIndex <= 1)
{ //don't do these things on non-humanoids
vec3_t lookAngles;
entityState_t *emplaced = NULL;
if (cent->currentState.hasLookTarget)
{
VectorSubtract(cg_entities[cent->currentState.lookTarget].lerpOrigin, cent->lerpOrigin, lookAngles);
vectoangles(lookAngles, lookAngles);
ci->lookTime = cg.time + 1000;
}
else
{
VectorCopy(cent->lerpAngles, lookAngles);
}
lookAngles[PITCH] = 0;
if (cent->currentState.otherEntityNum2)
{
emplaced = &cg_entities[cent->currentState.otherEntityNum2].currentState;
}
BG_G2PlayerAngles(cent->ghoul2, ci->bolt_motion, &cent->currentState, cg.time,
cent->lerpOrigin, cent->lerpAngles, legs, legsAngles, &cent->pe.torso.yawing, &cent->pe.torso.pitching,
&cent->pe.legs.yawing, &cent->pe.torso.yawAngle, &cent->pe.torso.pitchAngle, &cent->pe.legs.yawAngle,
cg.frametime, cent->turAngles, cent->modelScale, ci->legsAnim, ci->torsoAnim, &ci->corrTime,
lookAngles, ci->lastHeadAngles, ci->lookTime, emplaced, &ci->superSmoothTime);
if (cent->currentState.heldByClient && cent->currentState.heldByClient <= MAX_CLIENTS)
{ //then put our arm in this client's hand
//is index+1 because index 0 is valid.
int heldByIndex = cent->currentState.heldByClient-1;
centity_t *other = &cg_entities[heldByIndex];
if (other && other->ghoul2 && ci->bolt_lhand)
{
mdxaBone_t boltMatrix;
vec3_t boltOrg;
trap_G2API_GetBoltMatrix(other->ghoul2, 0, ci->bolt_lhand, &boltMatrix, other->turAngles, other->lerpOrigin, cg.time, cgs.gameModels, other->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg);
BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg.time, &cent->currentState,
cent->currentState.torsoAnim/*BOTH_DEAD1*/, boltOrg, &cent->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qfalse);
}
}
else if (cent->ikStatus)
{ //make sure we aren't IKing if we don't have anyone to hold onto us.
BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg.time, &cent->currentState,
cent->currentState.torsoAnim/*BOTH_DEAD1*/, vec3_origin, &cent->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qtrue);
}
}
else if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
{
vec3_t lookAngles;
VectorCopy(cent->lerpAngles, legsAngles);
legsAngles[PITCH] = 0;
AnglesToAxis( legsAngles, legs );
VectorCopy(cent->lerpAngles, lookAngles);
lookAngles[YAW] = lookAngles[ROLL] = 0;
BG_G2ATSTAngles( cent->ghoul2, cg.time, lookAngles );
}
else
{
if (cent->currentState.eType == ET_NPC &&
cent->currentState.NPC_class == CLASS_VEHICLE &&
cent->m_pVehicle &&
cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
{ //fighters actually want to take pitch and roll into account for the axial angles
VectorCopy(cent->lerpAngles, legsAngles);
AnglesToAxis( legsAngles, legs );
}
else if (cent->currentState.eType == ET_NPC &&
cent->currentState.m_iVehicleNum &&
cent->currentState.NPC_class != CLASS_VEHICLE )
{ //an NPC bolted to a vehicle should use the full angles
VectorCopy(cent->lerpAngles, legsAngles);
AnglesToAxis( legsAngles, legs );
}
else
{
vec3_t nhAngles;
if (cent->currentState.eType == ET_NPC &&
cent->currentState.NPC_class == CLASS_VEHICLE &&
cent->m_pVehicle &&
cent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
{ //yeah, a hack, sorry.
VectorSet(nhAngles, 0, cent->lerpAngles[YAW], cent->lerpAngles[ROLL]);
}
else
{
VectorSet(nhAngles, 0, cent->lerpAngles[YAW], 0);
}
AnglesToAxis( nhAngles, legs );
}
}
//See if we have any bone angles sent from the server
CG_G2ServerBoneAngles(cent);
}
//==========================================================================
/*
===============
CG_TrailItem
===============
*/
#if 0
static 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, -16, axis[0], ent.origin );
ent.origin[2] += 16;
angles[YAW] += 90;
AnglesToAxis( angles, ent.axis );
ent.hModel = hModel;
trap_R_AddRefEntityToScene( &ent );
}
#endif
/*
===============
CG_PlayerFlag
===============
*/
static void CG_PlayerFlag( centity_t *cent, qhandle_t hModel ) {
refEntity_t ent;
vec3_t angles;
vec3_t axis[3];
vec3_t boltOrg, tAng, getAng, right;
mdxaBone_t boltMatrix;
clientInfo_t *ci;
if (cent->currentState.number == cg.snap->ps.clientNum &&
!cg.renderingThirdPerson)
{
return;
}
if (!cent->ghoul2)
{
return;
}
if (cent->currentState.eType == ET_NPC)
{
ci = cent->npcClient;
assert(ci);
}
else
{
ci = &cgs.clientinfo[cent->currentState.number];
}
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_llumbar, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg);
BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, tAng);
vectoangles(tAng, tAng);
VectorCopy(cent->lerpAngles, angles);
boltOrg[2] -= 12;
VectorSet(getAng, 0, cent->lerpAngles[1], 0);
AngleVectors(getAng, 0, right, 0);
boltOrg[0] += right[0]*8;
boltOrg[1] += right[1]*8;
boltOrg[2] += right[2]*8;
angles[PITCH] = -cent->lerpAngles[PITCH]/2-30;
angles[YAW] = tAng[YAW]+270;
AnglesToAxis(angles, axis);
memset( &ent, 0, sizeof( ent ) );
VectorMA( boltOrg, 24, axis[0], ent.origin );
angles[ROLL] += 20;
AnglesToAxis( angles, ent.axis );
ent.hModel = hModel;
ent.modelScale[0] = 0.5;
ent.modelScale[1] = 0.5;
ent.modelScale[2] = 0.5;
ScaleModelAxis(&ent);
/*
if (cent->currentState.number == cg.snap->ps.clientNum)
{ //If we're the current client (in third person), render the flag on our back transparently
ent.renderfx |= RF_FORCE_ENT_ALPHA;
ent.shaderRGBA[3] = 100;
}
*/
//FIXME: Not doing this at the moment because sorting totally messes up
trap_R_AddRefEntityToScene( &ent );
}
/*
===============
CG_PlayerPowerups
===============
*/
static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) {
int powerups;
clientInfo_t *ci;
powerups = cent->currentState.powerups;
if ( !powerups ) {
return;
}
// quad gives a dlight
if ( powerups & ( 1 << PW_QUAD ) ) {
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 );
}
if (cent->currentState.eType == ET_NPC)
{
ci = cent->npcClient;
assert(ci);
}
else
{
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
}
// redflag
if ( powerups & ( 1 << PW_REDFLAG ) ) {
CG_PlayerFlag( cent, cgs.media.redFlagModel );
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f );
}
// blueflag
if ( powerups & ( 1 << PW_BLUEFLAG ) ) {
CG_PlayerFlag( cent, cgs.media.blueFlagModel );
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 );
}
// neutralflag
if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) {
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 );
}
// haste leaves smoke trails
/*
if ( powerups & ( 1 << PW_HASTE ) ) {
CG_HasteTrail( cent );
}
*/
}
/*
===============
CG_PlayerFloatSprite
Float a sprite over the player's head
===============
*/
static 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;
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 255;
ent.shaderRGBA[3] = 255;
trap_R_AddRefEntityToScene( &ent );
}
/*
===============
CG_PlayerFloatSprite
Same as above but allows custom RGBA values
===============
*/
#if 0
static void CG_PlayerFloatSpriteRGBA( centity_t *cent, qhandle_t shader, vec4_t rgba ) {
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;
ent.shaderRGBA[0] = rgba[0];
ent.shaderRGBA[1] = rgba[1];
ent.shaderRGBA[2] = rgba[2];
ent.shaderRGBA[3] = rgba[3];
trap_R_AddRefEntityToScene( &ent );
}
#endif
/*
===============
CG_PlayerSprites
Float sprites over the player's head
===============
*/
static void CG_PlayerSprites( centity_t *cent ) {
// int team;
if (cg.snap &&
CG_IsMindTricked(cent->currentState.trickedentindex,
cent->currentState.trickedentindex2,
cent->currentState.trickedentindex3,
cent->currentState.trickedentindex4,
cg.snap->ps.clientNum))
{
return; //this entity is mind-tricking the current client, so don't render it
}
if ( cent->currentState.eFlags & EF_CONNECTION ) {
CG_PlayerFloatSprite( cent, cgs.media.connectionShader );
return;
}
if (cent->vChatTime > cg.time)
{
CG_PlayerFloatSprite( cent, cgs.media.vchatShader );
}
else if ( cent->currentState.eType != ET_NPC && //don't draw talk balloons on NPCs
(cent->currentState.eFlags & EF_TALK) )
{
CG_PlayerFloatSprite( cent, cgs.media.balloonShader );
return;
}
}
/*
===============
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 = {-15, -15, 0}, maxs = {15, 15, 2};
trace_t trace;
float alpha;
float radius = 24.0f;
*shadowPlane = 0;
if ( cg_shadows.integer == 0 ) {
return qfalse;
}
// no shadows when cloaked
if ( cent->currentState.powerups & ( 1 << PW_CLOAKED ))
{
return qfalse;
}
if (cent->currentState.eFlags & EF_DEAD)
{
return qfalse;
}
if (CG_IsMindTricked(cent->currentState.trickedentindex,
cent->currentState.trickedentindex2,
cent->currentState.trickedentindex3,
cent->currentState.trickedentindex4,
cg.snap->ps.clientNum))
{
return qfalse; //this entity is mind-tricking the current client, so don't render it
}
if ( cg_shadows.integer == 1 )
{//dropshadow
if (cent->currentState.m_iVehicleNum &&
cent->currentState.NPC_class != CLASS_VEHICLE )
{//riding a vehicle, no dropshadow
return qfalse;
}
}
// send a trace down from the player to the ground
VectorCopy( cent->lerpOrigin, end );
if (cg_shadows.integer == 2)
{ //stencil
end[2] -= 4096.0f;
trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid )
{
trace.endpos[2] = cent->lerpOrigin[2]-25.0f;
}
}
else
{
end[2] -= SHADOW_DISTANCE;
trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
// no shadow if too high
if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) {
return qfalse;
}
}
if (cg_shadows.integer == 2)
{ //stencil shadows need plane to be on ground
*shadowPlane = trace.endpos[2];
}
else
{
*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;
// bk0101022 - hack / FPE - bogus planes?
//assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f )
// add the mark as a temporary, so it goes directly to the renderer
// without taking a spot in the cg_marks array
if ( cent->currentState.NPC_class == CLASS_REMOTE
|| cent->currentState.NPC_class == CLASS_SEEKER )
{
radius = 8.0f;
}
CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, radius, qtrue );
return qtrue;
}
/*
===============
CG_PlayerSplash
Draw a mark at the water surface
===============
*/
static void CG_PlayerSplash( centity_t *cent ) {
vec3_t start, end;
trace_t trace;
int contents;
polyVert_t verts[4];
if ( !cg_shadows.integer ) {
return;
}
VectorCopy( cent->lerpOrigin, end );
end[2] -= 24;
// if the feet aren't in liquid, don't make a mark
// this won't handle moving water brushes, but they wouldn't draw right anyway...
contents = trap_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 = trap_CM_PointContents( start, 0 );
if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
return;
}
// trace down to find the surface
trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
if ( trace.fraction == 1.0 ) {
return;
}
// create a mark polygon
VectorCopy( trace.endpos, verts[0].xyz );
verts[0].xyz[0] -= 32;
verts[0].xyz[1] -= 32;
verts[0].st[0] = 0;
verts[0].st[1] = 0;
verts[0].modulate[0] = 255;
verts[0].modulate[1] = 255;
verts[0].modulate[2] = 255;
verts[0].modulate[3] = 255;
VectorCopy( trace.endpos, verts[1].xyz );
verts[1].xyz[0] -= 32;
verts[1].xyz[1] += 32;
verts[1].st[0] = 0;
verts[1].st[1] = 1;
verts[1].modulate[0] = 255;
verts[1].modulate[1] = 255;
verts[1].modulate[2] = 255;
verts[1].modulate[3] = 255;
VectorCopy( trace.endpos, verts[2].xyz );
verts[2].xyz[0] += 32;
verts[2].xyz[1] += 32;
verts[2].st[0] = 1;
verts[2].st[1] = 1;
verts[2].modulate[0] = 255;
verts[2].modulate[1] = 255;
verts[2].modulate[2] = 255;
verts[2].modulate[3] = 255;
VectorCopy( trace.endpos, verts[3].xyz );
verts[3].xyz[0] += 32;
verts[3].xyz[1] -= 32;
verts[3].st[0] = 1;
verts[3].st[1] = 0;
verts[3].modulate[0] = 255;
verts[3].modulate[1] = 255;
verts[3].modulate[2] = 255;
verts[3].modulate[3] = 255;
trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts );
}
#define REFRACT_EFFECT_DURATION 500
static void CG_ForcePushBlur( vec3_t org, centity_t *cent )
{
if (!cent || !cg_renderToTextureFX.integer)
{
localEntity_t *ex;
ex = CG_AllocLocalEntity();
ex->leType = LE_PUFF;
ex->refEntity.reType = RT_SPRITE;
ex->radius = 2.0f;
ex->startTime = cg.time;
ex->endTime = ex->startTime + 120;
VectorCopy( org, ex->pos.trBase );
ex->pos.trTime = cg.time;
ex->pos.trType = TR_LINEAR;
VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta );
ex->color[0] = 24;
ex->color[1] = 32;
ex->color[2] = 40;
ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" );
ex = CG_AllocLocalEntity();
ex->leType = LE_PUFF;
ex->refEntity.reType = RT_SPRITE;
ex->refEntity.rotation = 180.0f;
ex->radius = 2.0f;
ex->startTime = cg.time;
ex->endTime = ex->startTime + 120;
VectorCopy( org, ex->pos.trBase );
ex->pos.trTime = cg.time;
ex->pos.trType = TR_LINEAR;
VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta );
ex->color[0] = 24;
ex->color[1] = 32;
ex->color[2] = 40;
ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" );
}
else
{ //superkewl "refraction" (well sort of) effect -rww
refEntity_t ent;
vec3_t ang;
float scale;
float vLen;
float alpha;
int tDif;
if (!cent->bodyFadeTime)
{ //the duration for the expansion and fade
cent->bodyFadeTime = cg.time + REFRACT_EFFECT_DURATION;
}
//closer tDif is to 0, the closer we are to
//being "done"
tDif = (cent->bodyFadeTime - cg.time);
if ((REFRACT_EFFECT_DURATION-tDif) < 200)
{ //stop following the hand after a little and stay in a fixed spot
//save the initial spot of the effect
VectorCopy(org, cent->pushEffectOrigin);
}
//scale from 1.0f to 0.1f then hold at 0.1 for the rest of the duration
if (cent->currentState.powerups & (1 << PW_PULL))
{
scale = (float)(REFRACT_EFFECT_DURATION-tDif)*0.003f;
}
else
{
scale = (float)(tDif)*0.003f;
}
if (scale > 1.0f)
{
scale = 1.0f;
}
else if (scale < 0.2f)
{
scale = 0.2f;
}
//start alpha at 244, fade to 10
alpha = (float)tDif*0.488f;
if (alpha > 244.0f)
{
alpha = 244.0f;
}
else if (alpha < 10.0f)
{
alpha = 10.0f;
}
memset( &ent, 0, sizeof( ent ) );
ent.shaderTime = (cent->bodyFadeTime-REFRACT_EFFECT_DURATION) / 1000.0f;
VectorCopy( cent->pushEffectOrigin, ent.origin );
VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]);
vLen = VectorLength(ent.axis[0]);
if (vLen <= 0.1f)
{ // Entity is right on vieworg. quit.
return;
}
vectoangles(ent.axis[0], ang);
ang[ROLL] += 180.0f;
AnglesToAxis(ang, ent.axis);
//radius must be a power of 2, and is the actual captured texture size
if (vLen < 128)
{
ent.radius = 256;
}
else if (vLen < 256)
{
ent.radius = 128;
}
else if (vLen < 512)
{
ent.radius = 64;
}
else
{
ent.radius = 32;
}
VectorScale(ent.axis[0], scale, ent.axis[0]);
VectorScale(ent.axis[1], scale, ent.axis[1]);
VectorScale(ent.axis[2], scale, ent.axis[2]);
ent.hModel = cgs.media.halfShieldModel;
ent.customShader = cgs.media.refractionShader; //cgs.media.cloakedShader;
ent.nonNormalizedAxes = qtrue;
//make it partially transparent so it blends with the background
ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA);
ent.shaderRGBA[0] = 255.0f;
ent.shaderRGBA[1] = 255.0f;
ent.shaderRGBA[2] = 255.0f;
ent.shaderRGBA[3] = alpha;
trap_R_AddRefEntityToScene( &ent );
}
}
static const char *cg_pushBoneNames[] =
{
"cranium",
"lower_lumbar",
"rhand",
"lhand",
"ltibia",
"rtibia",
"lradius",
"rradius",
NULL
};
static void CG_ForcePushBodyBlur( centity_t *cent )
{
vec3_t fxOrg;
mdxaBone_t boltMatrix;
int bolt;
int i;
if (cent->localAnimIndex > 1)
{ //Sorry, the humanoid IS IN ANOTHER CASTLE.
return;
}
if (cg.snap &&
CG_IsMindTricked(cent->currentState.trickedentindex,
cent->currentState.trickedentindex2,
cent->currentState.trickedentindex3,
cent->currentState.trickedentindex4,
cg.snap->ps.clientNum))
{
return; //this entity is mind-tricking the current client, so don't render it
}
assert(cent->ghoul2);
for (i = 0; cg_pushBoneNames[i]; i++)
{ //go through all the bones we want to put a blur effect on
bolt = trap_G2API_AddBolt(cent->ghoul2, 0, cg_pushBoneNames[i]);
if (bolt == -1)
{
assert(!"You've got an invalid bone/bolt name in cg_pushBoneNames");
continue;
}
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, fxOrg);
//standard effect, don't be refractive (for now)
CG_ForcePushBlur(fxOrg, NULL);
}
}
static void CG_ForceGripEffect( vec3_t org )
{
localEntity_t *ex;
float wv = sin( cg.time * 0.004f ) * 0.08f + 0.1f;
ex = CG_AllocLocalEntity();
ex->leType = LE_PUFF;
ex->refEntity.reType = RT_SPRITE;
ex->radius = 2.0f;
ex->startTime = cg.time;
ex->endTime = ex->startTime + 120;
VectorCopy( org, ex->pos.trBase );
ex->pos.trTime = cg.time;
ex->pos.trType = TR_LINEAR;
VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta );
ex->color[0] = 200+((wv*255));
if (ex->color[0] > 255)
{
ex->color[0] = 255;
}
ex->color[1] = 0;
ex->color[2] = 0;
ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" );
ex = CG_AllocLocalEntity();
ex->leType = LE_PUFF;
ex->refEntity.reType = RT_SPRITE;
ex->refEntity.rotation = 180.0f;
ex->radius = 2.0f;
ex->startTime = cg.time;
ex->endTime = ex->startTime + 120;
VectorCopy( org, ex->pos.trBase );
ex->pos.trTime = cg.time;
ex->pos.trType = TR_LINEAR;
VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta );
/*
ex->color[0] = 200+((wv*255));
if (ex->color[0] > 255)
{
ex->color[0] = 255;
}
*/
ex->color[0] = 255;
ex->color[1] = 255;
ex->color[2] = 255;
ex->refEntity.customShader = cgs.media.redSaberGlowShader;//trap_R_RegisterShader( "gfx/effects/forcePush" );
}
/*
===============
CG_AddRefEntityWithPowerups
Adds a piece with modifications or duplications for powerups
Also called by CG_Missile for quad rockets, but nobody can tell...
===============
*/
void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) {
if (CG_IsMindTricked(state->trickedentindex,
state->trickedentindex2,
state->trickedentindex3,
state->trickedentindex4,
cg.snap->ps.clientNum))
{
return; //this entity is mind-tricking the current client, so don't render it
}
trap_R_AddRefEntityToScene( ent );
}
#define MAX_SHIELD_TIME 2000.0
#define MIN_SHIELD_TIME 2000.0
void CG_PlayerShieldHit(int entitynum, vec3_t dir, int amount)
{
centity_t *cent;
int time;
if (entitynum<0 || entitynum >= MAX_ENTITIES)
{
return;
}
cent = &cg_entities[entitynum];
if (amount > 100)
{
time = cg.time + MAX_SHIELD_TIME; // 2 sec.
}
else
{
time = cg.time + 500 + amount*15;
}
if (time > cent->damageTime)
{
cent->damageTime = time;
VectorScale(dir, -1, dir);
vectoangles(dir, cent->damageAngles);
}
}
void CG_DrawPlayerShield(centity_t *cent, vec3_t origin)
{
refEntity_t ent;
int alpha;
float scale;
// Don't draw the shield when the player is dead.
if (cent->currentState.eFlags & EF_DEAD)
{
return;
}
memset( &ent, 0, sizeof( ent ) );
VectorCopy( origin, ent.origin );
ent.origin[2] += 10.0;
AnglesToAxis( cent->damageAngles, ent.axis );
alpha = 255.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + random()*16;
if (alpha>255)
alpha=255;
// Make it bigger, but tighter if more solid
scale = 1.4 - ((float)alpha*(0.4/255.0)); // Range from 1.0 to 1.4
VectorScale( ent.axis[0], scale, ent.axis[0] );
VectorScale( ent.axis[1], scale, ent.axis[1] );
VectorScale( ent.axis[2], scale, ent.axis[2] );
ent.hModel = cgs.media.halfShieldModel;
ent.customShader = cgs.media.halfShieldShader;
ent.shaderRGBA[0] = alpha;
ent.shaderRGBA[1] = alpha;
ent.shaderRGBA[2] = alpha;
ent.shaderRGBA[3] = 255;
trap_R_AddRefEntityToScene( &ent );
}
void CG_PlayerHitFX(centity_t *cent)
{
// only do the below fx if the cent in question is...uh...me, and it's first person.
if (cent->currentState.clientNum != cg.predictedPlayerState.clientNum || cg.renderingThirdPerson)
{
if (cent->damageTime > cg.time
&& cent->currentState.NPC_class != CLASS_VEHICLE )
{
CG_DrawPlayerShield(cent, cent->lerpOrigin);
}
return;
}
}
/*
=================
CG_LightVerts
=================
*/
int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts )
{
int i, j;
float incoming;
vec3_t ambientLight;
vec3_t lightDir;
vec3_t directedLight;
trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir );
for (i = 0; i < numVerts; i++) {
incoming = DotProduct (normal, lightDir);
if ( incoming <= 0 ) {
verts[i].modulate[0] = ambientLight[0];
verts[i].modulate[1] = ambientLight[1];
verts[i].modulate[2] = ambientLight[2];
verts[i].modulate[3] = 255;
continue;
}
j = ( ambientLight[0] + incoming * directedLight[0] );
if ( j > 255 ) {
j = 255;
}
verts[i].modulate[0] = j;
j = ( ambientLight[1] + incoming * directedLight[1] );
if ( j > 255 ) {
j = 255;
}
verts[i].modulate[1] = j;
j = ( ambientLight[2] + incoming * directedLight[2] );
if ( j > 255 ) {
j = 255;
}
verts[i].modulate[2] = j;
verts[i].modulate[3] = 255;
}
return qtrue;
}
static void CG_RGBForSaberColor( saber_colors_t color, vec3_t rgb )
{
switch( color )
{
case SABER_RED:
VectorSet( rgb, 1.0f, 0.2f, 0.2f );
break;
case SABER_ORANGE:
VectorSet( rgb, 1.0f, 0.5f, 0.1f );
break;
case SABER_YELLOW:
VectorSet( rgb, 1.0f, 1.0f, 0.2f );
break;
case SABER_GREEN:
VectorSet( rgb, 0.2f, 1.0f, 0.2f );
break;
case SABER_BLUE:
VectorSet( rgb, 0.2f, 0.4f, 1.0f );
break;
case SABER_PURPLE:
VectorSet( rgb, 0.9f, 0.2f, 1.0f );
break;
}
}
static void CG_DoSaberLight( saberInfo_t *saber )
{
vec3_t positions[MAX_BLADES*2], mid={0}, rgbs[MAX_BLADES*2], rgb={0};
float lengths[MAX_BLADES*2]={0}, totallength = 0, numpositions = 0, dist, diameter = 0;
int i, j;
//RGB combine all the colors of the sabers you're using into one averaged color!
if ( !saber )
{
return;
}
if ( (saber->saberFlags2&SFL2_NO_DLIGHT) )
{//no dlight!
return;
}
for ( i = 0; i < saber->numBlades; i++ )
{
if ( saber->blade[i].length >= 0.5f )
{
//FIXME: make RGB sabers
CG_RGBForSaberColor( saber->blade[i].color, rgbs[i] );
lengths[i] = saber->blade[i].length;
if ( saber->blade[i].length*2.0f > diameter )
{
diameter = saber->blade[i].length*2.0f;
}
totallength += saber->blade[i].length;
VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length, saber->blade[i].muzzleDir, positions[i] );
if ( !numpositions )
{//first blade, store middle of that as midpoint
VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length*0.5, saber->blade[i].muzzleDir, mid );
VectorCopy( rgbs[i], rgb );
}
numpositions++;
}
}
if ( totallength )
{//actually have something to do
if ( numpositions == 1 )
{//only 1 blade, midpoint is already set (halfway between the start and end of that blade), rgb is already set, so it diameter
}
else
{//multiple blades, calc averages
VectorClear( mid );
VectorClear( rgb );
//now go through all the data and get the average RGB and middle position and the radius
for ( i = 0; i < MAX_BLADES*2; i++ )
{
if ( lengths[i] )
{
VectorMA( rgb, lengths[i], rgbs[i], rgb );
VectorAdd( mid, positions[i], mid );
}
}
//get middle rgb
VectorScale( rgb, 1/totallength, rgb );//get the average, normalized RGB
//get mid position
VectorScale( mid, 1/numpositions, mid );
//find the farthest distance between the blade tips, this will be our diameter
for ( i = 0; i < MAX_BLADES*2; i++ )
{
if ( lengths[i] )
{
for ( j = 0; j < MAX_BLADES*2; j++ )
{
if ( lengths[j] )
{
dist = Distance( positions[i], positions[j] );
if ( dist > diameter )
{
diameter = dist;
}
}
}
}
}
}
trap_R_AddLightToScene( mid, diameter + (random()*8.0f), rgb[0], rgb[1], rgb[2] );
}
}
void CG_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, float radius, saber_colors_t color, int rfx, qboolean doLight )
{
vec3_t mid;
qhandle_t blade = 0, glow = 0;
refEntity_t saber;
float radiusmult;
float radiusRange;
float radiusStart;
if ( length < 0.5f )
{
// if the thing is so short, just forget even adding me.
return;
}
// Find the midpoint of the saber for lighting purposes
VectorMA( origin, length * 0.5f, dir, mid );
switch( color )
{
case SABER_RED:
glow = cgs.media.redSaberGlowShader;
blade = cgs.media.redSaberCoreShader;
break;
case SABER_ORANGE:
glow = cgs.media.orangeSaberGlowShader;
blade = cgs.media.orangeSaberCoreShader;
break;
case SABER_YELLOW:
glow = cgs.media.yellowSaberGlowShader;
blade = cgs.media.yellowSaberCoreShader;
break;
case SABER_GREEN:
glow = cgs.media.greenSaberGlowShader;
blade = cgs.media.greenSaberCoreShader;
break;
case SABER_BLUE:
glow = cgs.media.blueSaberGlowShader;
blade = cgs.media.blueSaberCoreShader;
break;
case SABER_PURPLE:
glow = cgs.media.purpleSaberGlowShader;
blade = cgs.media.purpleSaberCoreShader;
break;
default:
glow = cgs.media.blueSaberGlowShader;
blade = cgs.media.blueSaberCoreShader;
break;
}
if (doLight)
{ // always add a light because sabers cast a nice glow before they slice you in half!! or something...
vec3_t rgb={1,1,1};
CG_RGBForSaberColor( color, rgb );
trap_R_AddLightToScene( mid, (length*1.4f) + (random()*3.0f), rgb[0], rgb[1], rgb[2] );
}
memset( &saber, 0, sizeof( refEntity_t ));
// Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many
// refEnts to do each glow blob individually
saber.saberLength = length;
// Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed.
// It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat
if (length < lengthMax)
{
radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5.
}
else
{
radiusmult = 1.0;
}
if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4)
{ //draw the blade as a post-render so it doesn't get in the cap...
rfx |= RF_FORCEPOST;
}
radiusRange = radius * 0.075f;
radiusStart = radius-radiusRange;
saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult;
//saber.radius = (2.8f + crandom() * 0.2f)*radiusmult;
VectorCopy( origin, saber.origin );
VectorCopy( dir, saber.axis[0] );
saber.reType = RT_SABER_GLOW;
saber.customShader = glow;
saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff;
saber.renderfx = rfx;
trap_R_AddRefEntityToScene( &saber );
// Do the hot core
VectorMA( origin, length, dir, saber.origin );
VectorMA( origin, -1, dir, saber.oldorigin );
// CG_TestLine(saber.origin, saber.oldorigin, 50, 0x000000ff, 3);
saber.customShader = blade;
saber.reType = RT_LINE;
radiusStart = radius/3.0f;
saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult;
// saber.radius = (1.0 + crandom() * 0.2f)*radiusmult;
saber.shaderTexCoord[0] = saber.shaderTexCoord[1] = 1.0f;
saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff;
trap_R_AddRefEntityToScene( &saber );
}
//--------------------------------------------------------------
// 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;
int i = 0;
// Get the requested tag
trap_R_LerpTag( &orientation, model->hModel, model->oldframe, model->frame,
1.0f - model->backlerp, tag );
VectorCopy( model->origin, pos );
for ( i = 0 ; i < 3 ; i++ )
{
VectorMA( pos, orientation.origin[i], model->axis[i], pos );
}
if ( axis )
{
MatrixMultiply( orientation.axis, model->axis, axis );
}
}
#define MAX_MARK_FRAGMENTS 128
#define MAX_MARK_POINTS 384
extern markPoly_t *CG_AllocMark();
void CG_CreateSaberMarks( vec3_t start, vec3_t end, vec3_t normal )
{
// byte colors[4];
int i, j;
int numFragments;
vec3_t axis[3], originalPoints[4], mid;
vec3_t markPoints[MAX_MARK_POINTS], projection;
polyVert_t *v, verts[MAX_VERTS_ON_POLY];
markPoly_t *mark;
markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf;
float radius = 0.65f;
if ( !cg_addMarks.integer )
{
return;
}
VectorSubtract( end, start, axis[1] );
VectorNormalize( axis[1] );
// create the texture axis
VectorCopy( normal, axis[0] );
CrossProduct( axis[1], axis[0], axis[2] );
// create the full polygon that we'll project
for ( i = 0 ; i < 3 ; i++ )
{ // stretch a bit more in the direction that we are traveling in... debateable as to whether this makes things better or worse
originalPoints[0][i] = start[i] - radius * axis[1][i] - radius * axis[2][i];
originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i];
originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i];
originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i];
}
VectorScale( normal, -1, projection );
// get the fragments
numFragments = trap_CM_MarkFragments( 4, (const float (*)[3])originalPoints,
projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments );
for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ )
{
// we have an upper limit on the complexity of polygons that we store persistantly
if ( mf->numPoints > MAX_VERTS_ON_POLY )
{
mf->numPoints = MAX_VERTS_ON_POLY;
}
for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ )
{
vec3_t delta;
// Set up our texture coords, this may need some work
VectorCopy( markPoints[mf->firstPoint + j], v->xyz );
VectorAdd( end, start, mid );
VectorScale( mid, 0.5f, mid );
VectorSubtract( v->xyz, mid, delta );
v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * (0.05f + random() * 0.03f);
v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.15f + random() * 0.05f);
}
if (cg_saberDynamicMarks.integer)
{
int i = 0;
int i_2 = 0;
addpolyArgStruct_t apArgs;
vec3_t x;
memset (&apArgs, 0, sizeof(apArgs));
while (i < 4)
{
while (i_2 < 3)
{
apArgs.p[i][i_2] = verts[i].xyz[i_2];
i_2++;
}
i_2 = 0;
i++;
}
i = 0;
i_2 = 0;
while (i < 4)
{
while (i_2 < 2)
{
apArgs.ev[i][i_2] = verts[i].st[i_2];
i_2++;
}
i_2 = 0;
i++;
}
//When using addpoly, having a situation like this tends to cause bad results.
//(I assume it doesn't like trying to draw a polygon over two planes and extends
//the vertex out to some odd value)
VectorSubtract(apArgs.p[0], apArgs.p[3], x);
if (VectorLength(x) > 3.0f)
{
return;
}
apArgs.numVerts = mf->numPoints;
VectorCopy(vec3_origin, apArgs.vel);
VectorCopy(vec3_origin, apArgs.accel);
apArgs.alpha1 = 1.0f;
apArgs.alpha2 = 0.0f;
apArgs.alphaParm = 255.0f;
VectorSet(apArgs.rgb1, 0.0f, 0.0f, 0.0f);
VectorSet(apArgs.rgb2, 0.0f, 0.0f, 0.0f);
apArgs.rgbParm = 0.0f;
apArgs.bounce = 0;
apArgs.motionDelay = 0;
apArgs.killTime = cg_saberDynamicMarkTime.integer;
apArgs.shader = cgs.media.rivetMarkShader;
apArgs.flags = 0x08000000|0x00000004;
trap_FX_AddPoly(&apArgs);
apArgs.shader = cgs.media.mSaberDamageGlow;
apArgs.rgb1[0] = 215 + random() * 40.0f;
apArgs.rgb1[1] = 96 + random() * 32.0f;
apArgs.rgb1[2] = apArgs.alphaParm = random()*15.0f;
apArgs.rgb1[0] /= 255;
apArgs.rgb1[1] /= 255;
apArgs.rgb1[2] /= 255;
VectorCopy(apArgs.rgb1, apArgs.rgb2);
apArgs.killTime = 100;
trap_FX_AddPoly(&apArgs);
}
else
{
// save it persistantly, do burn first
mark = CG_AllocMark();
mark->time = cg.time;
mark->alphaFade = qtrue;
mark->markShader = cgs.media.rivetMarkShader;
mark->poly.numVerts = mf->numPoints;
mark->color[0] = mark->color[1] = mark->color[2] = mark->color[3] = 255;
memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
// And now do a glow pass
// by moving the start time back, we can hack it to fade out way before the burn does
mark = CG_AllocMark();
mark->time = cg.time - 8500;
mark->alphaFade = qfalse;
mark->markShader = cgs.media.mSaberDamageGlow;
mark->poly.numVerts = mf->numPoints;
mark->color[0] = 215 + random() * 40.0f;
mark->color[1] = 96 + random() * 32.0f;
mark->color[2] = mark->color[3] = random()*15.0f;
memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
}
}
}
qboolean CG_G2TraceCollide(trace_t *tr, vec3_t const mins, vec3_t const maxs, const vec3_t lastValidStart, const vec3_t lastValidEnd)
{
G2Trace_t G2Trace;
centity_t *g2Hit;
vec3_t angles;
int tN = 0;
float fRadius = 0.0f;
if (mins && maxs &&
(mins[0] || maxs[0]))
{
fRadius=(maxs[0]-mins[0])/2.0f;
}
memset (&G2Trace, 0, sizeof(G2Trace));
while (tN < MAX_G2_COLLISIONS)
{
G2Trace[tN].mEntityNum = -1;
tN++;
}
g2Hit = &cg_entities[tr->entityNum];
if (g2Hit && g2Hit->ghoul2)
{
angles[ROLL] = angles[PITCH] = 0;
angles[YAW] = g2Hit->lerpAngles[YAW];
if (cg_optvehtrace.integer &&
g2Hit->currentState.eType == ET_NPC &&
g2Hit->currentState.NPC_class == CLASS_VEHICLE &&
g2Hit->m_pVehicle)
{
trap_G2API_CollisionDetectCache ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg.time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius );
}
else
{
trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg.time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius );
}
if (G2Trace[0].mEntityNum != g2Hit->currentState.number)
{
tr->fraction = 1.0f;
tr->entityNum = ENTITYNUM_NONE;
tr->startsolid = 0;
tr->allsolid = 0;
return qfalse;
}
else
{ //Yay!
VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos);
VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal);
return qtrue;
}
}
return qfalse;
}
void CG_G2SaberEffects(vec3_t start, vec3_t end, centity_t *owner)
{
trace_t trace;
vec3_t startTr;
vec3_t endTr;
qboolean backWards = qfalse;
qboolean doneWithTraces = qfalse;
while (!doneWithTraces)
{
if (!backWards)
{
VectorCopy(start, startTr);
VectorCopy(end, endTr);
}
else
{
VectorCopy(end, startTr);
VectorCopy(start, endTr);
}
CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID );
if (trace.entityNum < MAX_CLIENTS)
{ //hit a client..
CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr);
if (trace.entityNum != ENTITYNUM_NONE)
{ //it succeeded with the ghoul2 trace
trap_FX_PlayEffectID( cgs.effects.mSaberBloodSparks, trace.endpos, trace.plane.normal, -1, -1 );
trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3))));
}
}
if (!backWards)
{
backWards = qtrue;
}
else
{
doneWithTraces = qtrue;
}
}
}
#define CG_MAX_SABER_COMP_TIME 400 //last registered saber entity hit must match within this many ms for the client effect to take place.
void CG_AddGhoul2Mark(int shader, float size, vec3_t start, vec3_t end, int entnum,
vec3_t entposition, float entangle, void *ghoul2, vec3_t scale, int lifeTime)
{
SSkinGoreData goreSkin;
assert(ghoul2);
memset ( &goreSkin, 0, sizeof(goreSkin) );
if (trap_G2API_GetNumGoreMarks(ghoul2, 0) >= cg_ghoul2Marks.integer)
{ //you've got too many marks already
return;
}
goreSkin.growDuration = -1; // default expandy time
goreSkin.goreScaleStartFraction = 1.0; // default start scale
goreSkin.frontFaces = qtrue;
goreSkin.backFaces = qtrue;
goreSkin.lifeTime = lifeTime; //last randomly 10-20 seconds
/*
if (lifeTime)
{
goreSkin.fadeOutTime = lifeTime*0.1; //default fade duration is relative to lifetime.
}
goreSkin.fadeRGB = qtrue; //fade on RGB instead of alpha (this depends on the shader really, modify if needed)
*/
//rwwFIXMEFIXME: fade has sorting issues with other non-fading decals, disabled until fixed
goreSkin.baseModelOnly = qfalse;
goreSkin.currentTime = cg.time;
goreSkin.entNum = entnum;
goreSkin.SSize = size;
goreSkin.TSize = size;
goreSkin.theta = flrand(0.0f,6.28f);
goreSkin.shader = shader;
if (!scale[0] && !scale[1] && !scale[2])
{
VectorSet(goreSkin.scale, 1.0f, 1.0f, 1.0f);
}
else
{
VectorCopy(goreSkin.scale, scale);
}
VectorCopy (start, goreSkin.hitLocation);
VectorSubtract(end, start, goreSkin.rayDirection);
if (VectorNormalize(goreSkin.rayDirection)<.1f)
{
return;
}
VectorCopy ( entposition, goreSkin.position );
goreSkin.angles[YAW] = entangle;
trap_G2API_AddSkinGore(ghoul2, &goreSkin);
}
void CG_SaberCompWork(vec3_t start, vec3_t end, centity_t *owner, int saberNum, int bladeNum)
{
trace_t trace;
vec3_t startTr;
vec3_t endTr;
qboolean backWards = qfalse;
qboolean doneWithTraces = qfalse;
qboolean doEffect = qfalse;
clientInfo_t *client = NULL;
if ((cg.time - owner->serverSaberHitTime) > CG_MAX_SABER_COMP_TIME)
{
return;
}
if (cg.time == owner->serverSaberHitTime)
{ //don't want to do it the same frame as the server hit, to avoid burst effect concentrations every x ms.
return;
}
while (!doneWithTraces)
{
if (!backWards)
{
VectorCopy(start, startTr);
VectorCopy(end, endTr);
}
else
{
VectorCopy(end, startTr);
VectorCopy(start, endTr);
}
CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID );
if (trace.entityNum == owner->serverSaberHitIndex)
{ //this is the guy the server says we last hit, so continue.
if (cg_entities[trace.entityNum].ghoul2)
{ //If it has a g2 instance, do the proper ghoul2 checks
CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr);
if (trace.entityNum != ENTITYNUM_NONE)
{ //it succeeded with the ghoul2 trace
doEffect = qtrue;
if (cg_ghoul2Marks.integer)
{
vec3_t ePos;
centity_t *trEnt = &cg_entities[trace.entityNum];
if (trEnt->ghoul2)
{
if (trEnt->currentState.eType != ET_NPC ||
trEnt->currentState.NPC_class != CLASS_VEHICLE ||
!trEnt->m_pVehicle ||
trEnt->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER)
{ //don't do on fighters cause they have crazy full axial angles
int weaponMarkShader = 0, markShader = cgs.media.bdecal_saberglow;
VectorSubtract(endTr, trace.endpos, ePos);
VectorNormalize(ePos);
VectorMA(trace.endpos, 4.0f, ePos, ePos);
if (owner->currentState.eType == ET_NPC)
{
client = owner->npcClient;
}
else
{
client = &cgs.clientinfo[owner->currentState.clientNum];
}
if ( client
&& client->infoValid )
{
if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) )
{
if ( client->saber[saberNum].g2MarksShader2 )
{//we have a shader to use instead of the standard mark shader
markShader = client->saber[saberNum].g2MarksShader2;
}
if ( client->saber[saberNum].g2WeaponMarkShader2 )
{//we have a shader to use as a splashback onto the weapon model
weaponMarkShader = client->saber[saberNum].g2WeaponMarkShader2;
}
}
else
{
if ( client->saber[saberNum].g2MarksShader )
{//we have a shader to use instead of the standard mark shader
markShader = client->saber[saberNum].g2MarksShader;
}
if ( client->saber[saberNum].g2WeaponMarkShader )
{//we have a shader to use as a splashback onto the weapon model
weaponMarkShader = client->saber[saberNum].g2WeaponMarkShader;
}
}
}
CG_AddGhoul2Mark(markShader, flrand(3.0f, 4.0f),
trace.endpos, ePos, trace.entityNum, trEnt->lerpOrigin, trEnt->lerpAngles[YAW],
trEnt->ghoul2, trEnt->modelScale, Q_irand(5000, 10000));
if ( weaponMarkShader )
{
vec3_t splashBackDir;
VectorScale( ePos, -1 , splashBackDir );
CG_AddGhoul2Mark(weaponMarkShader, flrand(0.5f, 2.0f),
trace.endpos, splashBackDir, owner->currentState.clientNum, owner->lerpOrigin, owner->lerpAngles[YAW],
owner->ghoul2, owner->modelScale, Q_irand(5000, 10000));
}
}
}
}
}
}
else
{ //otherwise, we're all set.
doEffect = qtrue;
}
if (doEffect)
{
int hitPersonFxID = cgs.effects.mSaberBloodSparks;
int hitOtherFxID = cgs.effects.mSaberCut;
if (owner->currentState.eType == ET_NPC)
{
client = owner->npcClient;
}
else
{
client = &cgs.clientinfo[owner->currentState.clientNum];
}
if ( client && client->infoValid )
{
if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) )
{//use second blade style values
if ( client->saber[saberNum].hitPersonEffect2 )
{
hitPersonFxID = client->saber[saberNum].hitPersonEffect2;
}
if ( client->saber[saberNum].hitOtherEffect2 )
{//custom hit other effect
hitOtherFxID = client->saber[saberNum].hitOtherEffect2;
}
}
else
{//use first blade style values
if ( client->saber[saberNum].hitPersonEffect )
{
hitPersonFxID = client->saber[saberNum].hitPersonEffect;
}
if ( client->saber[saberNum].hitOtherEffect )
{//custom hit other effect
hitOtherFxID = client->saber[saberNum].hitOtherEffect;
}
}
}
if (!trace.plane.normal[0] && !trace.plane.normal[1] && !trace.plane.normal[2])
{ //who cares, just shoot it somewhere.
trace.plane.normal[1] = 1;
}
if (owner->serverSaberFleshImpact)
{ //do standard player/live ent hit sparks
trap_FX_PlayEffectID( hitPersonFxID, trace.endpos, trace.plane.normal, -1, -1 );
//trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3))));
}
else
{ //do the cut effect
trap_FX_PlayEffectID( hitOtherFxID, trace.endpos, trace.plane.normal, -1, -1 );
}
doEffect = qfalse;
}
}
/*
if (!backWards)
{
backWards = qtrue;
}
else
{
doneWithTraces = qtrue;
}
*/
doneWithTraces = qtrue; //disabling backwards tr for now, sometimes it just makes too many effects.
}
}
#define SABER_TRAIL_TIME 40.0f
#define FX_USE_ALPHA 0x08000000
#include "../namespace_begin.h"
qboolean BG_SuperBreakWinAnim( int anim );
#include "../namespace_end.h"
void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, int saberNum, int bladeNum, vec3_t origin, vec3_t angles, qboolean fromSaber, qboolean dontDraw)
{
vec3_t org_, end, v,
axis_[3] = {0,0,0, 0,0,0, 0,0,0}; // shut the compiler up
trace_t trace;
int i = 0;
int trailDur;
float saberLen;
float diff;
clientInfo_t *client;
centity_t *saberEnt;
saberTrail_t *saberTrail;
mdxaBone_t boltMatrix;
vec3_t futureAngles;
effectTrailArgStruct_t fx;
int scolor = 0;
int useModelIndex = 0;
if (cent->currentState.eType == ET_NPC)
{
client = cent->npcClient;
assert(client);
}
else
{
client = &cgs.clientinfo[cent->currentState.number];
}
saberEnt = &cg_entities[cent->currentState.saberEntityNum];
saberLen = client->saber[saberNum].blade[bladeNum].length;
if (saberLen <= 0 && !dontDraw)
{ //don't bother then.
return;
}
futureAngles[YAW] = angles[YAW];
futureAngles[PITCH] = angles[PITCH];
futureAngles[ROLL] = angles[ROLL];
if ( fromSaber )
{
useModelIndex = 0;
}
else
{
useModelIndex = saberNum+1;
}
//Assume bladeNum is equal to the bolt index because bolts should be added in order of the blades.
//if there is an effect on this blade, play it
if ( !WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum )
&& client->saber[saberNum].bladeEffect )
{
trap_FX_PlayBoltedEffectID(client->saber[saberNum].bladeEffect, scent->lerpOrigin,
scent->ghoul2, bladeNum, scent->currentState.number, useModelIndex, -1, qfalse);
}
else if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum )
&& client->saber[saberNum].bladeEffect2 )
{
trap_FX_PlayBoltedEffectID(client->saber[saberNum].bladeEffect2, scent->lerpOrigin,
scent->ghoul2, bladeNum, scent->currentState.number, useModelIndex, -1, qfalse);
}
//get the boltMatrix
trap_G2API_GetBoltMatrix(scent->ghoul2, useModelIndex, bladeNum, &boltMatrix, futureAngles, origin, cg.time, cgs.gameModels, scent->modelScale);
// work the matrix axis stuff into the original axis and origins used.
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, org_);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, axis_[0]);
if (!fromSaber && saberEnt && !cent->currentState.saberInFlight)
{
VectorCopy(org_, saberEnt->currentState.pos.trBase);
VectorCopy(axis_[0], saberEnt->currentState.apos.trBase);
}
VectorMA( org_, saberLen, axis_[0], end );
VectorAdd( end, axis_[0], end );
if (cent->currentState.eType == ET_NPC)
{
scolor = client->saber[saberNum].blade[bladeNum].color;
}
else
{
if (saberNum == 0)
{
scolor = client->icolor1;
}
else
{
scolor = client->icolor2;
}
}
if (cgs.gametype >= GT_TEAM &&
cgs.gametype != GT_SIEGE &&
!cgs.jediVmerc &&
cent->currentState.eType != ET_NPC)
{
if (client->team == TEAM_RED)
{
scolor = SABER_RED;
}
else if (client->team == TEAM_BLUE)
{
scolor = SABER_BLUE;
}
}
if (!cg_saberContact.integer)
{ //if we don't have saber contact enabled, just add the blade and don't care what it's touching
goto CheckTrail;
}
if (!dontDraw)
{
if (cg_saberModelTraceEffect.integer)
{
CG_G2SaberEffects(org_, end, cent);
}
else if (cg_saberClientVisualCompensation.integer)
{
CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID );
if (trace.fraction != 1)
{ //nudge the endpos a very small amount from the beginning to the end, so the comp trace hits at the end.
//I'm only bothering with this because I want to do a backwards trace too in the comp trace, so if the
//blade is sticking through a player or something the standard trace doesn't it, it will make sparks
//on each side.
vec3_t seDif;
VectorSubtract(trace.endpos, org_, seDif);
VectorNormalize(seDif);
trace.endpos[0] += seDif[0]*0.1f;
trace.endpos[1] += seDif[1]*0.1f;
trace.endpos[2] += seDif[2]*0.1f;
}
if (client->saber[saberNum].blade[bladeNum].storageTime < cg.time)
{ //debounce it in case our framerate is absurdly high. Using storageTime since it's not used for anything else in the client.
CG_SaberCompWork(org_, trace.endpos, cent, saberNum, bladeNum);
client->saber[saberNum].blade[bladeNum].storageTime = cg.time + 5;
}
}
for ( i = 0; i < 1; i++ )//was 2 because it would go through architecture and leave saber trails on either side of the brush - but still looks bad if we hit a corner, blade is still 8 longer than hit
{
if ( i )
{//tracing from end to base
CG_Trace( &trace, end, NULL, NULL, org_, ENTITYNUM_NONE, MASK_SOLID );
}
else
{//tracing from base to end
CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID );
}
if ( trace.fraction < 1.0f )
{
vec3_t trDir;
VectorCopy(trace.plane.normal, trDir);
if (!trDir[0] && !trDir[1] && !trDir[2])
{
trDir[1] = 1;
}
if ( (client->saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) )
{//don't actually draw the marks/impact effects
}
else
{
if (!(trace.surfaceFlags & SURF_NOIMPACT) ) // never spark on sky
{
trap_FX_PlayEffectID( cgs.effects.mSparks, trace.endpos, trDir, -1, -1 );
}
}
//Stop saber? (it wouldn't look right if it was stuck through a thin wall and unable to hurt players on the other side)
VectorSubtract(org_, trace.endpos, v);
saberLen = VectorLength(v);
VectorCopy(trace.endpos, end);
if ( (client->saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) )
{//don't actually draw the marks
}
else
{//draw marks if we hit a wall
// All I need is a bool to mark whether I have a previous point to work with.
//....come up with something better..
if ( client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] )
{
if ( trace.entityNum == ENTITYNUM_WORLD || cg_entities[trace.entityNum].currentState.eType == ET_TERRAIN || (cg_entities[trace.entityNum].currentState.eFlags & EF_PERMANENT) )
{//only put marks on architecture
// Let's do some cool burn/glowing mark bits!!!
CG_CreateSaberMarks( client->saber[saberNum].blade[bladeNum].trail.oldPos[i], trace.endpos, trace.plane.normal );
//make a sound
if ( cg.time - client->saber[saberNum].blade[bladeNum].hitWallDebounceTime >= 100 )
{//ugh, need to have a real sound debouncer... or do this game-side
client->saber[saberNum].blade[bladeNum].hitWallDebounceTime = cg.time;
trap_S_StartSound ( trace.endpos, -1, CHAN_WEAPON, trap_S_RegisterSound( va("sound/weapons/saber/saberhitwall%i", Q_irand(1, 3)) ) );
}
}
}
else
{
// if we impact next frame, we'll mark a slash mark
client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qtrue;
// CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i],
// 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse );
}
}
// stash point so we can connect-the-dots later
VectorCopy( trace.endpos, client->saber[saberNum].blade[bladeNum].trail.oldPos[i] );
VectorCopy( trace.plane.normal, client->saber[saberNum].blade[bladeNum].trail.oldNormal[i] );
}
else
{
if ( client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] )
{
// Hmmm, no impact this frame, but we have an old point
// Let's put the mark there, we should use an endcap mark to close the line, but we
// can probably just get away with a round mark
// CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i],
// 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse );
}
// we aren't impacting, so turn off our mark tracking mechanism
client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qfalse;
}
}
}
CheckTrail:
if (!cg_saberTrail.integer)
{ //don't do the trail in this case
goto JustDoIt;
}
if ( (!WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle > 1 )
|| ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle2 > 1 ) )
{//don't actually draw the trail at all
goto JustDoIt;
}
//FIXME: if trailStyle is 1, use the motion blur instead
saberTrail = &client->saber[saberNum].blade[bladeNum].trail;
saberTrail->duration = saberMoveData[cent->currentState.saberMove].trailLength;
trailDur = (saberTrail->duration/5.0f);
if (!trailDur)
{ //hmm.. ok, default
if ( BG_SuperBreakWinAnim(cent->currentState.torsoAnim) )
{
trailDur = 150;
}
else
{
trailDur = SABER_TRAIL_TIME;
}
}
// if we happen to be timescaled or running in a high framerate situation, we don't want to flood
// the system with very small trail slices...but perhaps doing it by distance would yield better results?
if ( cg.time > saberTrail->lastTime + 2 || cg_saberTrail.integer == 2 ) // 2ms
{
if (!dontDraw)
{
if ( (BG_SuperBreakWinAnim(cent->currentState.torsoAnim) || saberMoveData[cent->currentState.saberMove].trailLength > 0 || ((cent->currentState.powerups & (1 << PW_SPEED) && cg_speedTrail.integer)) || (cent->currentState.saberInFlight && saberNum == 0)) && cg.time < saberTrail->lastTime + 2000 ) // if we have a stale segment, don't draw until we have a fresh one
{
#if 0
if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4)
{
polyVert_t verts[4];
VectorCopy( org_, verts[0].xyz );
VectorMA( end, 3.0f, axis_[0], verts[1].xyz );
VectorCopy( saberTrail->tip, verts[2].xyz );
VectorCopy( saberTrail->base, verts[3].xyz );
//tc doesn't even matter since we're just gonna stencil an outline, but whatever.
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;
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;
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;
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;
//don't capture postrender objects (now we'll postrender the saber so it doesn't get in the capture)
trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue);
//shader 2 is always the crazy refractive shader.
trap_R_AddPolyToScene( 2, 4, verts );
}
else
#endif
{
vec3_t rgb1={255.0f,255.0f,255.0f};
switch( scolor )
{
case SABER_RED:
VectorSet( rgb1, 255.0f, 0.0f, 0.0f );
break;
case SABER_ORANGE:
VectorSet( rgb1, 255.0f, 64.0f, 0.0f );
break;
case SABER_YELLOW:
VectorSet( rgb1, 255.0f, 255.0f, 0.0f );
break;
case SABER_GREEN:
VectorSet( rgb1, 0.0f, 255.0f, 0.0f );
break;
case SABER_BLUE:
VectorSet( rgb1, 0.0f, 64.0f, 255.0f );
break;
case SABER_PURPLE:
VectorSet( rgb1, 220.0f, 0.0f, 255.0f );
break;
default:
VectorSet( rgb1, 0.0f, 64.0f, 255.0f );
break;
}
//Here we will use the happy process of filling a struct in with arguments and passing it to a trap function
//so that we can take the struct and fill in an actual CTrail type using the data within it once we get it
//into the effects area
// Go from new muzzle to new end...then to old end...back down to old muzzle...finally
// connect back to the new muzzle...this is our trail quad
VectorCopy( org_, fx.mVerts[0].origin );
VectorMA( end, 3.0f, axis_[0], fx.mVerts[1].origin );
VectorCopy( saberTrail->tip, fx.mVerts[2].origin );
VectorCopy( saberTrail->base, fx.mVerts[3].origin );
diff = cg.time - saberTrail->lastTime;
// I'm not sure that clipping this is really the best idea
//This prevents the trail from showing at all in low framerate situations.
//if ( diff <= SABER_TRAIL_TIME * 2 )
if ( diff <= 10000 )
{ //don't draw it if the last time is way out of date
float oldAlpha = 1.0f - ( diff / trailDur );
if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4)
{//does other stuff below
}
else
{
if ( (!WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle == 1 )
|| ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle2 == 1 ) )
{//motion trail
fx.mShader = cgs.media.swordTrailShader;
VectorSet( rgb1, 32.0f, 32.0f, 32.0f ); // make the sith sword trail pretty faint
trailDur *= 2.0f; // stay around twice as long?
}
else
{
fx.mShader = cgs.media.saberBlurShader;
}
fx.mKillTime = trailDur;
fx.mSetFlags = FX_USE_ALPHA;
}
// New muzzle
VectorCopy( rgb1, fx.mVerts[0].rgb );
fx.mVerts[0].alpha = 255.0f;
fx.mVerts[0].ST[0] = 0.0f;
fx.mVerts[0].ST[1] = 1.0f;
fx.mVerts[0].destST[0] = 1.0f;
fx.mVerts[0].destST[1] = 1.0f;
// new tip
VectorCopy( rgb1, fx.mVerts[1].rgb );
fx.mVerts[1].alpha = 255.0f;
fx.mVerts[1].ST[0] = 0.0f;
fx.mVerts[1].ST[1] = 0.0f;
fx.mVerts[1].destST[0] = 1.0f;
fx.mVerts[1].destST[1] = 0.0f;
// old tip
VectorCopy( rgb1, fx.mVerts[2].rgb );
fx.mVerts[2].alpha = 255.0f;
fx.mVerts[2].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want
fx.mVerts[2].ST[1] = 0.0f;
fx.mVerts[2].destST[0] = 1.0f + fx.mVerts[2].ST[0];
fx.mVerts[2].destST[1] = 0.0f;
// old muzzle
VectorCopy( rgb1, fx.mVerts[3].rgb );
fx.mVerts[3].alpha = 255.0f;
fx.mVerts[3].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want
fx.mVerts[3].ST[1] = 1.0f;
fx.mVerts[3].destST[0] = 1.0f + fx.mVerts[2].ST[0];
fx.mVerts[3].destST[1] = 1.0f;
if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4)
{
trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); //don't need to do this every frame.. but..
if (BG_SaberInAttack(cent->currentState.saberMove)
||BG_SuperBreakWinAnim(cent->currentState.torsoAnim))
{ //in attack, strong trail
fx.mKillTime = 300;
}
else
{ //faded trail
fx.mKillTime = 40;
}
fx.mShader = 2; //2 is always refractive shader
fx.mSetFlags = FX_USE_ALPHA;
}
/*
else
{
fx.mShader = cgs.media.saberBlurShader;
fx.mKillTime = trailDur;
fx.mSetFlags = FX_USE_ALPHA;
}
*/
trap_FX_AddPrimitive(&fx);
}
}
}
}
// we must always do this, even if we aren't active..otherwise we won't know where to pick up from
VectorCopy( org_, saberTrail->base );
VectorMA( end, 3.0f, axis_[0], saberTrail->tip );
saberTrail->lastTime = cg.time;
}
JustDoIt:
if (dontDraw)
{
return;
}
if ( (client->saber[saberNum].saberFlags2&SFL2_NO_BLADE) )
{//don't actually draw the blade at all
if ( client->saber[saberNum].numBlades < 3
&& !(client->saber[saberNum].saberFlags2&SFL2_NO_DLIGHT) )
{//hmm, but still add the dlight
CG_DoSaberLight( &client->saber[saberNum] );
}
return;
}
// Pass in the renderfx flags attached to the saber weapon model...this is done so that saber glows
// will get rendered properly in a mirror...not sure if this is necessary??
//CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius,
// scolor, renderfx, (qboolean)(saberNum==0&&bladeNum==0) );
CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius,
scolor, renderfx, (qboolean)(client->saber[saberNum].numBlades < 3 && !(client->saber[saberNum].saberFlags2&SFL2_NO_DLIGHT)) );
}
int CG_IsMindTricked(int trickIndex1, int trickIndex2, int trickIndex3, int trickIndex4, int client)
{
int checkIn;
int sub = 0;
if (cg_entities[client].currentState.forcePowersActive & (1 << FP_SEE))
{
return 0;
}
if (client > 47)
{
checkIn = trickIndex4;
sub = 48;
}
else if (client > 31)
{
checkIn = trickIndex3;
sub = 32;
}
else if (client > 15)
{
checkIn = trickIndex2;
sub = 16;
}
else
{
checkIn = trickIndex1;
}
if (checkIn & (1 << (client-sub)))
{
return 1;
}
return 0;
}
#define SPEED_TRAIL_DISTANCE 6
void CG_DrawPlayerSphere(centity_t *cent, vec3_t origin, float scale, int shader)
{
refEntity_t ent;
vec3_t ang;
float vLen;
vec3_t viewDir;
// Don't draw the shield when the player is dead.
if (cent->currentState.eFlags & EF_DEAD)
{
return;
}
memset( &ent, 0, sizeof( ent ) );
VectorCopy( origin, ent.origin );
ent.origin[2] += 9.0;
VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]);
vLen = VectorLength(ent.axis[0]);
if (vLen <= 0.1f)
{ // Entity is right on vieworg. quit.
return;
}
VectorCopy(ent.axis[0], viewDir);
VectorInverse(viewDir);
VectorNormalize(viewDir);
vectoangles(ent.axis[0], ang);
ang[ROLL] += 180.0f;
ang[PITCH] += 180.0f;
AnglesToAxis(ang, ent.axis);
VectorScale(ent.axis[0], scale, ent.axis[0]);
VectorScale(ent.axis[1], scale, ent.axis[1]);
VectorScale(ent.axis[2], scale, ent.axis[2]);
ent.nonNormalizedAxes = qtrue;
ent.hModel = cgs.media.halfShieldModel;
ent.customShader = shader;
trap_R_AddRefEntityToScene( &ent );
if (!cg.renderingThirdPerson && cent->currentState.number == cg.predictedPlayerState.clientNum)
{ //don't do the rest then
return;
}
if (!cg_renderToTextureFX.integer)
{
return;
}
ang[PITCH] -= 180.0f;
AnglesToAxis(ang, ent.axis);
VectorScale(ent.axis[0], scale*0.5f, ent.axis[0]);
VectorScale(ent.axis[1], scale*0.5f, ent.axis[1]);
VectorScale(ent.axis[2], scale*0.5f, ent.axis[2]);
ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA);
if (shader == cgs.media.invulnerabilityShader)
{ //ok, ok, this is a little hacky. sorry!
ent.shaderRGBA[0] = 0;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 0;
ent.shaderRGBA[3] = 100;
}
else if (shader == cgs.media.ysalimariShader)
{
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 0;
ent.shaderRGBA[3] = 100;
}
else if (shader == cgs.media.endarkenmentShader)
{
ent.shaderRGBA[0] = 100;
ent.shaderRGBA[1] = 0;
ent.shaderRGBA[2] = 0;
ent.shaderRGBA[3] = 20;
}
else if (shader == cgs.media.enlightenmentShader)
{
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 255;
ent.shaderRGBA[3] = 20;
}
else
{ //ysal red/blue, boon
ent.shaderRGBA[0] = 255.0f;
ent.shaderRGBA[1] = 255.0f;
ent.shaderRGBA[2] = 255.0f;
ent.shaderRGBA[3] = 20;
}
ent.radius = 256;
VectorMA(ent.origin, 40.0f, viewDir, ent.origin);
ent.customShader = trap_R_RegisterShader("effects/refract_2");
trap_R_AddRefEntityToScene( &ent );
}
void CG_AddLightningBeam(vec3_t start, vec3_t end)
{
vec3_t dir, chaos,
c1, c2,
v1, v2;
float len,
s1, s2, s3;
addbezierArgStruct_t b;
VectorCopy(start, b.start);
VectorCopy(end, b.end);
VectorSubtract( b.end, b.start, dir );
len = VectorNormalize( dir );
// Get the base control points, we'll work from there
VectorMA( b.start, 0.3333f * len, dir, c1 );
VectorMA( b.start, 0.6666f * len, dir, c2 );
// get some chaos values that really aren't very chaotic :)
s1 = sin( cg.time * 0.005f ) * 2 + crandom() * 0.2f;
s2 = sin( cg.time * 0.001f );
s3 = sin( cg.time * 0.011f );
VectorSet( chaos, len * 0.01f * s1,
len * 0.02f * s2,
len * 0.04f * (s1 + s2 + s3));
VectorAdd( c1, chaos, c1 );
VectorScale( chaos, 4.0f, v1 );
VectorSet( chaos, -len * 0.02f * s3,
len * 0.01f * (s1 * s2),
-len * 0.02f * (s1 + s2 * s3));
VectorAdd( c2, chaos, c2 );
VectorScale( chaos, 2.0f, v2 );
VectorSet( chaos, 1.0f, 1.0f, 1.0f );
VectorCopy(c1, b.control1);
VectorCopy(vec3_origin, b.control1Vel);
VectorCopy(c2, b.control2);
VectorCopy(vec3_origin, b.control2Vel);
b.size1 = 6.0f;
b.size2 = 6.0f;
b.sizeParm = 0.0f;
b.alpha1 = 0.0f;
b.alpha2 = 0.2f;
b.alphaParm = 0.5f;
/*
VectorCopy(WHITE, b.sRGB);
VectorCopy(WHITE, b.eRGB);
*/
b.sRGB[0] = 255;
b.sRGB[1] = 255;
b.sRGB[2] = 255;
VectorCopy(b.sRGB, b.eRGB);
b.rgbParm = 0.0f;
b.killTime = 50;
b.shader = trap_R_RegisterShader( "gfx/misc/electric2" );
b.flags = 0x00000001; //FX_ALPHA_LINEAR
trap_FX_AddBezier(&b);
}
void CG_AddRandomLightning(vec3_t start, vec3_t end)
{
vec3_t inOrg, outOrg;
VectorCopy(start, inOrg);
VectorCopy(end, outOrg);
if ( rand() & 1 )
{
outOrg[0] += Q_irand(0, 24);
inOrg[0] += Q_irand(0, 8);
}
else
{
outOrg[0] -= Q_irand(0, 24);
inOrg[0] -= Q_irand(0, 8);
}
if ( rand() & 1 )
{
outOrg[1] += Q_irand(0, 24);
inOrg[1] += Q_irand(0, 8);
}
else
{
outOrg[1] -= Q_irand(0, 24);
inOrg[1] -= Q_irand(0, 8);
}
if ( rand() & 1 )
{
outOrg[2] += Q_irand(0, 50);
inOrg[2] += Q_irand(0, 40);
}
else
{
outOrg[2] -= Q_irand(0, 64);
inOrg[2] -= Q_irand(0, 40);
}
CG_AddLightningBeam(inOrg, outOrg);
}
extern char *forceHolocronModels[];
qboolean CG_ThereIsAMaster(void)
{
int i = 0;
centity_t *cent;
while (i < MAX_CLIENTS)
{
cent = &cg_entities[i];
if (cent && cent->currentState.isJediMaster)
{
return qtrue;
}
i++;
}
return qfalse;
}
#if 0
void CG_DrawNoForceSphere(centity_t *cent, vec3_t origin, float scale, int shader)
{
refEntity_t ent;
// Don't draw the shield when the player is dead.
if (cent->currentState.eFlags & EF_DEAD)
{
return;
}
memset( &ent, 0, sizeof( ent ) );
VectorCopy( origin, ent.origin );
ent.origin[2] += 9.0;
VectorSubtract(cg.refdef.vieworg, ent.origin, ent.axis[0]);
if (VectorNormalize(ent.axis[0]) <= 0.1f)
{ // Entity is right on vieworg. quit.
return;
}
VectorCopy(cg.refdef.viewaxis[2], ent.axis[2]);
CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]);
VectorScale(ent.axis[0], scale, ent.axis[0]);
VectorScale(ent.axis[1], scale, ent.axis[1]);
VectorScale(ent.axis[2], -scale, ent.axis[2]);
ent.shaderRGBA[3] = (cent->currentState.genericenemyindex - cg.time)/8;
ent.renderfx |= RF_RGB_TINT;
if (ent.shaderRGBA[3] > 200)
{
ent.shaderRGBA[3] = 200;
}
if (ent.shaderRGBA[3] < 1)
{
ent.shaderRGBA[3] = 1;
}
ent.shaderRGBA[2] = 0;
ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[3];
ent.hModel = cgs.media.halfShieldModel;
ent.customShader = shader;
trap_R_AddRefEntityToScene( &ent );
}
#endif
//Checks to see if the model string has a * appended with a custom skin name after.
//If so, it terminates the model string correctly, parses the skin name out, and returns
//the handle of the registered skin.
int CG_HandleAppendedSkin(char *modelName)
{
char skinName[MAX_QPATH];
char *p;
qhandle_t skinID = 0;
int i = 0;
//see if it has a skin name
p = Q_strrchr(modelName, '*');
if (p)
{ //found a *, we should have a model name before it and a skin name after it.
*p = 0; //terminate the modelName string at this point, then go ahead and parse to the next 0 for the skin.
p++;
while (p && *p)
{
skinName[i] = *p;
i++;
p++;
}
skinName[i] = 0;
if (skinName[0])
{ //got it, register the skin under the model path.
char baseFolder[MAX_QPATH];
strcpy(baseFolder, modelName);
p = Q_strrchr(baseFolder, '/'); //go back to the first /, should be the path point
if (p)
{ //got it.. terminate at the slash and register.
char *useSkinName;
*p = 0;
if (strchr(skinName, '|'))
{//three part skin
useSkinName = va("%s/|%s", baseFolder, skinName);
}
else
{
useSkinName = va("%s/model_%s.skin", baseFolder, skinName);
}
skinID = trap_R_RegisterSkin(useSkinName);
}
}
}
return skinID;
}
//Create a temporary ghoul2 instance and get the gla name so we can try loading animation data and sounds.
#include "../namespace_begin.h"
void BG_GetVehicleModelName(char *modelname);
void BG_GetVehicleSkinName(char *skinname);
#include "../namespace_end.h"
void CG_CacheG2AnimInfo(char *modelName)
{
void *g2 = NULL;
char *slash;
char useModel[MAX_QPATH];
char useSkin[MAX_QPATH];
int animIndex;
strcpy(useModel, modelName);
strcpy(useSkin, modelName);
if (modelName[0] == '$')
{ //it's a vehicle name actually, let's precache the whole vehicle
BG_GetVehicleModelName(useModel);
BG_GetVehicleSkinName(useSkin);
if ( useSkin[0] )
{ //use a custom skin
trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", useModel, useSkin));
}
else
{
trap_R_RegisterSkin(va("models/players/%s/model_default.skin", useModel));
}
strcpy(useModel, va("models/players/%s/model.glm", useModel));
}
trap_G2API_InitGhoul2Model(&g2, useModel, 0, 0, 0, 0, 0);
if (g2)
{
char GLAName[MAX_QPATH];
char originalModelName[MAX_QPATH];
animIndex = -1;
GLAName[0] = 0;
trap_G2API_GetGLAName(g2, 0, GLAName);
strcpy(originalModelName, useModel);
slash = Q_strrchr( GLAName, '/' );
if ( slash )
{
strcpy(slash, "/animation.cfg");
animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse);
}
if (animIndex != -1)
{
slash = Q_strrchr( originalModelName, '/' );
if ( slash )
{
slash++;
*slash = 0;
}
BG_ParseAnimationEvtFile(originalModelName, animIndex, bgNumAnimEvents);
}
//Now free the temp instance
trap_G2API_CleanGhoul2Models(&g2);
}
}
static void CG_RegisterVehicleAssets( Vehicle_t *pVeh )
{
/*
if ( pVeh->m_pVehicleInfo->exhaustFX )
{
pVeh->m_pVehicleInfo->iExhaustFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->exhaustFX );
}
if ( pVeh->m_pVehicleInfo->trailFX )
{
pVeh->m_pVehicleInfo->iTrailFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->trailFX );
}
if ( pVeh->m_pVehicleInfo->impactFX )
{
pVeh->m_pVehicleInfo->iImpactFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->impactFX );
}
if ( pVeh->m_pVehicleInfo->explodeFX )
{
pVeh->m_pVehicleInfo->iExplodeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->explodeFX );
}
if ( pVeh->m_pVehicleInfo->wakeFX )
{
pVeh->m_pVehicleInfo->iWakeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wakeFX );
}
if ( pVeh->m_pVehicleInfo->dmgFX )
{
pVeh->m_pVehicleInfo->iDmgFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->dmgFX );
}
if ( pVeh->m_pVehicleInfo->wpn1FX )
{
pVeh->m_pVehicleInfo->iWpn1FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FX );
}
if ( pVeh->m_pVehicleInfo->wpn2FX )
{
pVeh->m_pVehicleInfo->iWpn2FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FX );
}
if ( pVeh->m_pVehicleInfo->wpn1FireFX )
{
pVeh->m_pVehicleInfo->iWpn1FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FireFX );
}
if ( pVeh->m_pVehicleInfo->wpn2FireFX )
{
pVeh->m_pVehicleInfo->iWpn2FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FireFX );
}
*/
}
extern void CG_HandleNPCSounds(centity_t *cent);
#include "../namespace_begin.h"
extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType );
extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType );
extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType );
extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType );
#include "../namespace_end.h"
extern playerState_t *cgSendPS[MAX_GENTITIES];
void CG_G2AnimEntModelLoad(centity_t *cent)
{
const char *cModelName = CG_ConfigString( CS_MODELS+cent->currentState.modelindex );
if (!cent->npcClient)
{ //have not init'd client yet
return;
}
if (cModelName && cModelName[0])
{
char modelName[MAX_QPATH];
int skinID;
char *slash;
strcpy(modelName, cModelName);
if (cent->currentState.NPC_class == CLASS_VEHICLE && modelName[0] == '$')
{ //vehicles pass their veh names over as model names, then we get the model name from the veh type
//create a vehicle object clientside for this type
char *vehType = &modelName[1];
int iVehIndex = BG_VehicleGetIndex( vehType );
switch( g_vehicleInfo[iVehIndex].type )
{
case VH_ANIMAL:
// Create the animal (making sure all it's data is initialized).
G_CreateAnimalNPC( &cent->m_pVehicle, vehType );
break;
case VH_SPEEDER:
// Create the speeder (making sure all it's data is initialized).
G_CreateSpeederNPC( &cent->m_pVehicle, vehType );
break;
case VH_FIGHTER:
// Create the fighter (making sure all it's data is initialized).
G_CreateFighterNPC( &cent->m_pVehicle, vehType );
break;
case VH_WALKER:
// Create the walker (making sure all it's data is initialized).
G_CreateWalkerNPC( &cent->m_pVehicle, vehType );
break;
default:
assert(!"vehicle with an unknown type - couldn't create vehicle_t");
break;
}
//set up my happy prediction hack
cent->m_pVehicle->m_vOrientation = &cgSendPS[cent->currentState.number]->vehOrientation[0];
cent->m_pVehicle->m_pParentEntity = (bgEntity_t *)cent;
//attach the handles for fx cgame-side
CG_RegisterVehicleAssets(cent->m_pVehicle);
BG_GetVehicleModelName(modelName);
if (cent->m_pVehicle->m_pVehicleInfo->skin &&
cent->m_pVehicle->m_pVehicleInfo->skin[0])
{ //use a custom skin
skinID = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelName, cent->m_pVehicle->m_pVehicleInfo->skin));
}
else
{
skinID = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName));
}
strcpy(modelName, va("models/players/%s/model.glm", modelName));
//this sound is *only* used for vehicles now
cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" );
}
else
{
skinID = CG_HandleAppendedSkin(modelName); //get the skin if there is one.
}
if (cent->ghoul2)
{ //clean it first!
trap_G2API_CleanGhoul2Models(&cent->ghoul2);
}
trap_G2API_InitGhoul2Model(&cent->ghoul2, modelName, 0, skinID, 0, 0, 0);
if (cent->ghoul2)
{
char GLAName[MAX_QPATH];
char originalModelName[MAX_QPATH];
char *saber;
int j = 0;
if (cent->currentState.NPC_class == CLASS_VEHICLE &&
cent->m_pVehicle)
{ //do special vehicle stuff
char strTemp[128];
int i;
// Setup the default first bolt
i = trap_G2API_AddBolt( cent->ghoul2, 0, "model_root" );
// Setup the droid unit.
cent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( cent->ghoul2, 0, "*droidunit" );
// Setup the Exhausts.
for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ )
{
Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 );
cent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp );
}
// Setup the Muzzles.
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
{
Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 );
cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp );
if ( cent->m_pVehicle->m_iMuzzleTag[i] == -1 )
{//ergh, try *flash?
Com_sprintf( strTemp, 128, "*flash%i", i + 1 );
cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp );
}
}
// Setup the Turrets.
for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ )
{
if ( cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag )
{
cent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag );
}
else
{
cent->m_pVehicle->m_iGunnerViewTag[i] = -1;
}
}
}
if (cent->currentState.npcSaber1)
{
saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber1);
assert(!saber || !saber[0] || saber[0] == '@');
//valid saber names should always start with '@' for NPCs
if (saber && saber[0])
{
saber++; //skip over the @
WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 0, saber);
}
}
if (cent->currentState.npcSaber2)
{
saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber2);
assert(!saber || !saber[0] || saber[0] == '@');
//valid saber names should always start with '@' for NPCs
if (saber && saber[0])
{
saber++; //skip over the @
WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 1, saber);
}
}
// If this is a not vehicle, give it saber stuff...
if ( cent->currentState.NPC_class != CLASS_VEHICLE )
{
while (j < MAX_SABERS)
{
if (cent->npcClient->saber[j].model[0])
{
if (cent->npcClient->ghoul2Weapons[j])
{ //free the old instance(s)
trap_G2API_CleanGhoul2Models(&cent->npcClient->ghoul2Weapons[j]);
cent->npcClient->ghoul2Weapons[j] = 0;
}
CG_InitG2SaberData(j, cent->npcClient);
}
j++;
}
}
trap_G2API_SetSkin(cent->ghoul2, 0, skinID, skinID);
cent->localAnimIndex = -1;
GLAName[0] = 0;
trap_G2API_GetGLAName(cent->ghoul2, 0, GLAName);
strcpy(originalModelName, modelName);
if (GLAName[0] &&
!strstr(GLAName, "players/_humanoid/") /*&&
!strstr(GLAName, "players/rockettrooper/")*/)
{ //it doesn't use humanoid anims.
slash = Q_strrchr( GLAName, '/' );
if ( slash )
{
strcpy(slash, "/animation.cfg");
cent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse);
}
}
else
{ //humanoid index.
trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand");
trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand");
//rhand must always be first bolt. lhand always second. Whichever you want the
//jetpack bolted to must always be third.
trap_G2API_AddBolt(cent->ghoul2, 0, "*chestg");
//claw bolts
trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand_cap_r_arm");
trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand_cap_l_arm");
if (strstr(GLAName, "players/rockettrooper/"))
{
cent->localAnimIndex = 1;
}
else
{
cent->localAnimIndex = 0;
}
if (trap_G2API_AddBolt(cent->ghoul2, 0, "*head_top") == -1)
{
trap_G2API_AddBolt(cent->ghoul2, 0, "ceyebrow");
}
trap_G2API_AddBolt(cent->ghoul2, 0, "Motion");
}
// If this is a not vehicle...
if ( cent->currentState.NPC_class != CLASS_VEHICLE )
{
if (trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar") == -1)
{ //check now to see if we have this bone for setting anims and such
cent->noLumbar = qtrue;
}
if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1)
{ //check now to see if we have this bone for setting anims and such
cent->noFace = qtrue;
}
}
else
{
cent->noLumbar = qtrue;
cent->noFace = qtrue;
}
if (cent->localAnimIndex != -1)
{
slash = Q_strrchr( originalModelName, '/' );
if ( slash )
{
slash++;
*slash = 0;
}
cent->eventAnimIndex = BG_ParseAnimationEvtFile(originalModelName, cent->localAnimIndex, bgNumAnimEvents);
}
}
}
trap_S_ShutUp(qtrue);
CG_HandleNPCSounds(cent); //handle sound loading here as well.
trap_S_ShutUp(qfalse);
}
//for now this is just gonna create a big explosion on the area of the surface,
//because I am lazy.
static void CG_CreateSurfaceDebris(centity_t *cent, int surfNum, int fxID, qboolean throwPart)
{
int lostPartFX = 0;
int b = -1;
vec3_t v, d;
mdxaBone_t boltMatrix;
const char *surfName = NULL;
if ( surfNum > 0 )
{
surfName = bgToggleableSurfaces[surfNum];
}
if (!cent->ghoul2)
{ //oh no
return;
}
//let's add the surface as a bolt so we can get the base point of it
if (bgToggleableSurfaceDebris[surfNum] == 3)
{ //right wing flame
b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage");
if ( throwPart
&& cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo )
{
lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX;
}
}
else if (bgToggleableSurfaceDebris[surfNum] == 4)
{ //left wing flame
b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage");
if ( throwPart
&& cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo )
{
lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX;
}
}
else if (bgToggleableSurfaceDebris[surfNum] == 5)
{ //right wing flame 2
b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage");
if ( throwPart
&& cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo )
{
lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX;
}
}
else if (bgToggleableSurfaceDebris[surfNum] == 6)
{ //left wing flame 2
b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage");
if ( throwPart
&& cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo )
{
lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX;
}
}
else if (bgToggleableSurfaceDebris[surfNum] == 7)
{ //nose flame
b = trap_G2API_AddBolt(cent->ghoul2, 0, "*nosedamage");
if ( cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo )
{
lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iNoseFX;
}
}
else if ( surfName )
{
b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName);
}
if (b == -1 || surfNum == -1)
{ //couldn't find this surface apparently, so play on origin?
VectorCopy( cent->lerpOrigin, v );
AngleVectors( cent->lerpAngles, d, NULL, NULL );
VectorNormalize( d );
}
else
{
//now let's get the position and direction of this surface and make a big explosion
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time,
cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v);
BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d);
}
trap_FX_PlayEffectID(fxID, v, d, -1, -1);
if ( throwPart && lostPartFX )
{//throw off a ship part, too
vec3_t fxFwd;
AngleVectors( cent->lerpAngles, fxFwd, NULL, NULL );
trap_FX_PlayEffectID(lostPartFX, v, fxFwd, -1, -1);
}
}
//for now this is just gonna create a big explosion on the area of the surface,
//because I am lazy.
static void CG_CreateSurfaceSmoke(centity_t *cent, int shipSurf, int fxID)
{
int b = -1;
vec3_t v, d;
mdxaBone_t boltMatrix;
const char *surfName = NULL;
if (!cent->ghoul2)
{ //oh no
return;
}
//let's add the surface as a bolt so we can get the base point of it
if ( shipSurf == SHIPSURF_FRONT )
{ //front flame/smoke
surfName = "*nosedamage";
}
else if (shipSurf == SHIPSURF_BACK )
{ //back flame/smoke
surfName = "*exhaust1";//FIXME: random? Some point in-between?
}
else if (shipSurf == SHIPSURF_RIGHT )
{ //right wing flame/smoke
surfName = "*r_wingdamage";
}
else if (shipSurf == SHIPSURF_LEFT )
{ //left wing flame/smoke
surfName = "*l_wingdamage";
}
else
{//unknown surf!
return;
}
b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName);
if (b == -1)
{ //couldn't find this surface apparently
return;
}
//now let's get the position and direction of this surface and make a big explosion
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time,
cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v);
BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d);
trap_FX_PlayEffectID(fxID, v, d, -1, -1);
}
#define SMOOTH_G2ANIM_LERPANGLES
qboolean CG_VehicleShouldDrawShields( centity_t *vehCent )
{
if ( vehCent->damageTime > cg.time //ship shields currently taking damage
&& vehCent->currentState.NPC_class == CLASS_VEHICLE
&& vehCent->m_pVehicle
&& vehCent->m_pVehicle->m_pVehicleInfo )
{
return qtrue;
}
return qfalse;
}
/*
extern vmCvar_t cg_showVehBounds;
extern void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs,
int clientNum, int tracemask,
void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)); // bg_pmove.c
*/
qboolean CG_VehicleAttachDroidUnit( centity_t *droidCent, refEntity_t *legs )
{
if ( droidCent
&& droidCent->currentState.owner
&& droidCent->currentState.clientNum >= MAX_CLIENTS )
{//the only NPCs that can ride a vehicle are droids...???
centity_t *vehCent = &cg_entities[droidCent->currentState.owner];
if ( vehCent
&& vehCent->m_pVehicle
&& vehCent->ghoul2
&& vehCent->m_pVehicle->m_iDroidUnitTag != -1 )
{
mdxaBone_t boltMatrix;
vec3_t fwd, rt, tempAng;
trap_G2API_GetBoltMatrix(vehCent->ghoul2, 0, vehCent->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehCent->lerpAngles, vehCent->lerpOrigin, cg.time,
cgs.gameModels, vehCent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidCent->lerpOrigin);
BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, fwd);//WTF???
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, rt);//WTF???
vectoangles( fwd, droidCent->lerpAngles );
vectoangles( rt, tempAng );
droidCent->lerpAngles[ROLL] = tempAng[PITCH];
return qtrue;
}
}
return qfalse;
}
void CG_G2Animated( centity_t *cent )
{
#ifdef SMOOTH_G2ANIM_LERPANGLES
float angSmoothFactor = 0.7f;
#endif
if (!cent->ghoul2)
{ //Initialize this g2 anim ent, then return (will start rendering next frame)
CG_G2AnimEntModelLoad(cent);
cent->npcLocalSurfOff = 0;
cent->npcLocalSurfOn = 0;
return;
}
if (cent->npcLocalSurfOff != cent->currentState.surfacesOff ||
cent->npcLocalSurfOn != cent->currentState.surfacesOn)
{ //looks like it's time for an update.
int i = 0;
while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i])
{
if (!(cent->npcLocalSurfOff & (1 << i)) &&
(cent->currentState.surfacesOff & (1 << i)))
{ //it wasn't off before but it's off now, so reflect this change in the g2 instance.
if (bgToggleableSurfaceDebris[i] > 0)
{ //make some local debris of this thing?
//FIXME: throw off the proper model effect, too
CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestDestroyed, qtrue);
}
trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_OFF);
}
if (!(cent->npcLocalSurfOn & (1 << i)) &&
(cent->currentState.surfacesOn & (1 << i)))
{ //same as above, but on instead of off.
trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_ON);
}
i++;
}
cent->npcLocalSurfOff = cent->currentState.surfacesOff;
cent->npcLocalSurfOn = cent->currentState.surfacesOn;
}
/*
if (cent->currentState.weapon &&
!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1) &&
!(cent->currentState.eFlags & EF_DEAD))
{ //if the server says we have a weapon and we haven't copied one onto ourselves yet, then do so.
trap_G2API_CopySpecificGhoul2Model(g2WeaponInstances[cent->currentState.weapon], 0, cent->ghoul2, 1);
if (cent->currentState.weapon == WP_SABER)
{
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ));
}
}
*/
if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD))
{ //he's alive and has a limb missing still, reattach it and reset the weapon
CG_ReattachLimb(cent);
}
if (((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) && !cent->localAnimIndex)
{
vec3_t forcedAngles;
VectorClear(forcedAngles);
forcedAngles[YAW] = cent->lerpAngles[YAW];
CG_RagDoll(cent, forcedAngles);
}
#ifdef SMOOTH_G2ANIM_LERPANGLES
if ((cent->lerpAngles[YAW] > 0 && cent->smoothYaw < 0) ||
(cent->lerpAngles[YAW] < 0 && cent->smoothYaw > 0))
{ //keep it from snapping around on the threshold
cent->smoothYaw = -cent->smoothYaw;
}
cent->lerpAngles[YAW] = cent->smoothYaw+(cent->lerpAngles[YAW]-cent->smoothYaw)*angSmoothFactor;
cent->smoothYaw = cent->lerpAngles[YAW];
#endif
//now just render as a player
CG_Player(cent);
/*
if ( cg_showVehBounds.integer )
{//show vehicle bboxes
if ( cent->currentState.clientNum >= MAX_CLIENTS
&& cent->currentState.NPC_class == CLASS_VEHICLE
&& cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo
&& cent->currentState.clientNum != cg.predictedVehicleState.clientNum )
{//not the predicted vehicle
vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0};
vec3_t absmin, absmax;
vec3_t bmins, bmaxs;
float *old = cent->m_pVehicle->m_vOrientation;
cent->m_pVehicle->m_vOrientation = &cent->lerpAngles[0];
BG_VehicleAdjustBBoxForOrientation( cent->m_pVehicle, cent->lerpOrigin, bmins, bmaxs,
cent->currentState.number, MASK_PLAYERSOLID, NULL );
cent->m_pVehicle->m_vOrientation = old;
VectorAdd( cent->lerpOrigin, bmins, absmin );
VectorAdd( cent->lerpOrigin, bmaxs, absmax );
CG_Cube( absmin, absmax, NPCDEBUG_RED, 0.25 );
}
}
*/
}
//rww - here ends the majority of my g2animent stuff.
//Disabled for now, I'm too lazy to keep it working with all the stuff changing around.
#if 0
int cgFPLSState = 0;
void CG_ForceFPLSPlayerModel(centity_t *cent, clientInfo_t *ci)
{
animation_t *anim;
if (cg_fpls.integer && !cg.renderingThirdPerson)
{
int skinHandle;
skinHandle = trap_R_RegisterSkin("models/players/kyle/model_fpls2.skin");
trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model));
ci->torsoSkin = skinHandle;
trap_G2API_InitGhoul2Model(&ci->ghoul2Model, "models/players/kyle/model.glm", 0, ci->torsoSkin, 0, 0, 0);
ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand");
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg.time, -1, -1);
trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time);
trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time);
ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand");
//rhand must always be first bolt. lhand always second. Whichever you want the
//jetpack bolted to must always be third.
trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg");
//claw bolts
trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand_cap_r_arm");
trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand_cap_l_arm");
ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top");
if (ci->bolt_head == -1)
{
ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow");
}
ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion");
//We need a lower lumbar bolt for footsteps
ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar");
CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, ci->ghoul2Model);
}
else
{
CG_RegisterClientModelname(ci, ci->modelName, ci->skinName, ci->teamName, cent->currentState.number);
}
anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.legsAnim ];
if (anim)
{
int flags = BONE_ANIM_OVERRIDE_FREEZE;
int firstFrame = anim->firstFrame;
int setFrame = -1;
float animSpeed = 50.0f / anim->frameLerp;
if (anim->loopFrames != -1)
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames))
{
setFrame = cent->pe.legs.frame;
}
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150);
cent->currentState.legsAnim = 0;
}
anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ];
if (anim)
{
int flags = BONE_ANIM_OVERRIDE_FREEZE;
int firstFrame = anim->firstFrame;
int setFrame = -1;
float animSpeed = 50.0f / anim->frameLerp;
if (anim->loopFrames != -1)
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames))
{
setFrame = cent->pe.torso.frame;
}
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150);
cent->currentState.torsoAnim = 0;
}
trap_G2API_CleanGhoul2Models(&(cent->ghoul2));
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cent->ghoul2);
//Attach the instance to this entity num so we can make use of client-server
//shared operations if possible.
trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse);
}
#endif
//for allocating and freeing npc clientinfo structures.
//Remember to free this before game shutdown no matter what
//and don't stomp over it, as it is dynamic memory from the
//exe.
void CG_CreateNPCClient(clientInfo_t **ci)
{
//trap_TrueMalloc((void **)ci, sizeof(clientInfo_t));
*ci = (clientInfo_t *) BG_Alloc(sizeof(clientInfo_t));
}
void CG_DestroyNPCClient(clientInfo_t **ci)
{
memset(*ci, 0, sizeof(clientInfo_t));
//trap_TrueFree((void **)ci);
}
static void CG_ForceElectrocution( centity_t *cent, const vec3_t origin, vec3_t tempAngles, qhandle_t shader, qboolean alwaysDo )
{
// Undoing for now, at least this code should compile if I ( or anyone else ) decides to work on this effect
qboolean found = qfalse;
vec3_t fxOrg, fxOrg2, dir;
vec3_t rgb;
mdxaBone_t boltMatrix;
trace_t tr;
int bolt=-1;
int iter=0;
int torsoBolt = -1;
int crotchBolt = -1;
int elbowLBolt = -1;
int elbowRBolt = -1;
int handLBolt = -1;
int handRBolt = -1;
int kneeLBolt = -1;
int kneeRBolt = -1;
int footLBolt = -1;
int footRBolt = -1;
VectorSet(rgb, 1, 1, 1);
if (cent->localAnimIndex <= 1)
{ //humanoid
torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar");
crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis");
elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_arm_elbow");
elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_arm_elbow");
handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand");
handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand");
kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_l_knee");
kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_r_knee");
footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot");
footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot");
}
else if (cent->currentState.NPC_class == CLASS_PROTOCOL)
{ //any others that can use these bolts too?
torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar");
crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis");
elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_lg");
elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_rg");
handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hand_l");
handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*weapon");
kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_lg");
kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_rg");
footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_lg");
footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_rg");
}
// Pick a random start point
while (bolt<0)
{
int test;
if (iter>5)
{
test=iter-5;
}
else
{
test=Q_irand(0,6);
}
switch(test)
{
case 0:
// Right Elbow
bolt=elbowRBolt;
break;
case 1:
// Left Hand
bolt=handLBolt;
break;
case 2:
// Right hand
bolt=handRBolt;
break;
case 3:
// Left Foot
bolt=footLBolt;
break;
case 4:
// Right foot
bolt=footRBolt;
break;
case 5:
// Torso
bolt=torsoBolt;
break;
case 6:
default:
// Left Elbow
bolt=elbowLBolt;
break;
}
if (++iter==20)
break;
}
if (bolt>=0)
{
found = trap_G2API_GetBoltMatrix( cent->ghoul2, 0, bolt,
&boltMatrix, tempAngles, origin, cg.time,
cgs.gameModels, cent->modelScale);
}
// Make sure that it's safe to even try and get these values out of the Matrix, otherwise the values could be garbage
if ( found )
{
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, fxOrg );
if ( random() > 0.5f )
{
BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, dir );
}
else
{
BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir );
}
// Add some fudge, makes us not normalized, but that isn't really important
dir[0] += crandom() * 0.4f;
dir[1] += crandom() * 0.4f;
dir[2] += crandom() * 0.4f;
}
else
{
// Just use the lerp Origin and a random direction
VectorCopy( cent->lerpOrigin, fxOrg );
VectorSet( dir, crandom(), crandom(), crandom() ); // Not normalized, but who cares.
switch ( cent->currentState.NPC_class )
{
case CLASS_PROBE:
fxOrg[2] += 50;
break;
case CLASS_MARK1:
fxOrg[2] += 50;
break;
case CLASS_ATST:
fxOrg[2] += 120;
break;
default:
break;
}
}
VectorMA( fxOrg, random() * 40 + 40, dir, fxOrg2 );
CG_Trace( &tr, fxOrg, NULL, NULL, fxOrg2, -1, CONTENTS_SOLID );
if ( tr.fraction < 1.0f || random() > 0.94f || alwaysDo )
{
addElectricityArgStruct_t p;
VectorCopy(fxOrg, p.start);
VectorCopy(tr.endpos, p.end);
p.size1 = 1.5f;
p.size2 = 4.0f;
p.sizeParm = 0.0f;
p.alpha1 = 1.0f;
p.alpha2 = 0.5f;
p.alphaParm = 0.0f;
VectorCopy(rgb, p.sRGB);
VectorCopy(rgb, p.eRGB);
p.rgbParm = 0.0f;
p.chaos = 5.0f;
p.killTime = (random() * 50 + 100);
p.shader = shader;
p.flags = (0x00000001 | 0x00000100 | 0x02000000 | 0x04000000 | 0x01000000);
trap_FX_AddElectricity(&p);
//In other words:
/*
FX_AddElectricity( fxOrg, tr.endpos,
1.5f, 4.0f, 0.0f,
1.0f, 0.5f, 0.0f,
rgb, rgb, 0.0f,
5.5f, random() * 50 + 100, shader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR | FX_BRANCH | FX_GROW | FX_TAPER );
*/
}
}
void *cg_g2JetpackInstance = NULL;
#define JETPACK_MODEL "models/weapons2/jetpack/model.glm"
void CG_InitJetpackGhoul2(void)
{
if (cg_g2JetpackInstance)
{
assert(!"Tried to init jetpack inst, already init'd");
return;
}
trap_G2API_InitGhoul2Model(&cg_g2JetpackInstance, JETPACK_MODEL, 0, 0, 0, 0, 0);
assert(cg_g2JetpackInstance);
//Indicate which bolt on the player we will be attached to
//In this case bolt 0 is rhand, 1 is lhand, and 2 is the bolt
//for the jetpack (*chestg)
trap_G2API_SetBoltInfo(cg_g2JetpackInstance, 0, 2);
//Add the bolts jet effects will be played from
trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_ljet");
trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_rjet");
}
void CG_CleanJetpackGhoul2(void)
{
if (cg_g2JetpackInstance)
{
trap_G2API_CleanGhoul2Models(&cg_g2JetpackInstance);
cg_g2JetpackInstance = NULL;
}
}
#define RARMBIT (1 << (G2_MODELPART_RARM-10))
#define RHANDBIT (1 << (G2_MODELPART_RHAND-10))
#define WAISTBIT (1 << (G2_MODELPART_WAIST-10))
#if 0
static void CG_VehicleHeatEffect( vec3_t org, centity_t *cent )
{
refEntity_t ent;
vec3_t ang;
float scale;
float vLen;
float alpha;
if (!cg_renderToTextureFX.integer)
{
return;
}
scale = 0.1f;
alpha = 200.0f;
memset( &ent, 0, sizeof( ent ) );
VectorCopy( org, ent.origin );
VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]);
vLen = VectorLength(ent.axis[0]);
if (VectorNormalize(ent.axis[0]) <= 0.1f)
{ // Entity is right on vieworg. quit.
return;
}
vectoangles(ent.axis[0], ang);
AnglesToAxis(ang, ent.axis);
//radius must be a power of 2, and is the actual captured texture size
ent.radius = 32;
VectorScale(ent.axis[0], scale, ent.axis[0]);
VectorScale(ent.axis[1], scale, ent.axis[1]);
VectorScale(ent.axis[2], -scale, ent.axis[2]);
ent.hModel = cgs.media.halfShieldModel;
ent.customShader = cgs.media.cloakedShader;
//make it partially transparent so it blends with the background
ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA);
ent.shaderRGBA[0] = 255.0f;
ent.shaderRGBA[1] = 255.0f;
ent.shaderRGBA[2] = 255.0f;
ent.shaderRGBA[3] = alpha;
trap_R_AddRefEntityToScene( &ent );
}
#endif
static int lastFlyBySound[MAX_GENTITIES] = {0};
#define FLYBYSOUNDTIME 2000
int cg_lastHyperSpaceEffectTime = 0;
static CGAME_INLINE void CG_VehicleEffects(centity_t *cent)
{
Vehicle_t *pVehNPC;
if (cent->currentState.eType != ET_NPC ||
cent->currentState.NPC_class != CLASS_VEHICLE ||
!cent->m_pVehicle)
{
return;
}
pVehNPC = cent->m_pVehicle;
if ( cent->currentState.clientNum == cg.predictedPlayerState.m_iVehicleNum//my vehicle
&& (cent->currentState.eFlags2&EF2_HYPERSPACE) )//hyperspacing
{//in hyperspace!
if ( cg.predictedVehicleState.hyperSpaceTime
&& (cg.time-cg.predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME )
{
if ( !cg_lastHyperSpaceEffectTime
|| (cg.time - cg_lastHyperSpaceEffectTime) > HYPERSPACE_TIME+500 )
{//can't be from the last time we were in hyperspace, so play the effect!
trap_FX_PlayBoltedEffectID( cgs.effects.mHyperspaceStars, cent->lerpOrigin, cent->ghoul2, 0,
cent->currentState.number, 0, 0, qtrue );
cg_lastHyperSpaceEffectTime = cg.time;
}
}
}
//FLYBY sound
if ( cent->currentState.clientNum != cg.predictedPlayerState.m_iVehicleNum
&& (pVehNPC->m_pVehicleInfo->soundFlyBy||pVehNPC->m_pVehicleInfo->soundFlyBy2) )
{//not my vehicle
if ( cent->currentState.speed && cg.predictedPlayerState.speed+cent->currentState.speed > 500 )
{//he's moving and between the two of us, we're moving fast
vec3_t diff;
VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, diff );
if ( VectorLength( diff ) < 2048 )
{//close
vec3_t myFwd, theirFwd;
AngleVectors( cg.predictedPlayerState.viewangles, myFwd, NULL, NULL );
VectorScale( myFwd, cg.predictedPlayerState.speed, myFwd );
AngleVectors( cent->lerpAngles, theirFwd, NULL, NULL );
VectorScale( theirFwd, cent->currentState.speed, theirFwd );
if ( lastFlyBySound[cent->currentState.clientNum]+FLYBYSOUNDTIME < cg.time )
{//okay to do a flyby sound on this vehicle
if ( DotProduct( myFwd, theirFwd ) < 500 )
{
int flyBySound = 0;
if ( pVehNPC->m_pVehicleInfo->soundFlyBy && pVehNPC->m_pVehicleInfo->soundFlyBy2 )
{
flyBySound = Q_irand(0,1)?pVehNPC->m_pVehicleInfo->soundFlyBy:pVehNPC->m_pVehicleInfo->soundFlyBy2;
}
else if ( pVehNPC->m_pVehicleInfo->soundFlyBy )
{
flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy;
}
else //if ( pVehNPC->m_pVehicleInfo->soundFlyBy2 )
{
flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy2;
}
trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, flyBySound );
lastFlyBySound[cent->currentState.clientNum] = cg.time;
}
}
}
}
}
if ( !cent->currentState.speed//was stopped
&& cent->nextState.speed > 0//now moving forward
&& cent->m_pVehicle->m_pVehicleInfo->soundEngineStart )
{//engines rev up for the first time
trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, cent->m_pVehicle->m_pVehicleInfo->soundEngineStart );
}
// Animals don't exude any effects...
if ( pVehNPC->m_pVehicleInfo->type != VH_ANIMAL )
{
qboolean didFireTrail = qfalse;
if (pVehNPC->m_pVehicleInfo->surfDestruction && cent->ghoul2)
{ //see if anything has been blown off
int i = 0;
qboolean surfDmg = qfalse;
while (i < BG_NUM_TOGGLEABLE_SURFACES)
{
if (bgToggleableSurfaceDebris[i] > 1)
{ //this is decidedly a destroyable surface, let's check its status
int surfTest = trap_G2API_GetSurfaceRenderStatus(cent->ghoul2, 0, bgToggleableSurfaces[i]);
if ( surfTest != -1
&& (surfTest&TURN_OFF) )
{ //it exists, but it's off...
surfDmg = qtrue;
//create some flames
CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestBurning, qfalse);
didFireTrail = qtrue;
}
}
i++;
}
if (surfDmg)
{ //if any surface are damaged, neglect exhaust etc effects (so we don't have exhaust trails coming out of invisible surfaces)
return;
}
}
if ( !didFireTrail && (cent->currentState.eFlags&EF_DEAD) )
{//spiralling out of control anyway
CG_CreateSurfaceDebris(cent, -1, cgs.effects.mShipDestBurning, qfalse);
}
if ( pVehNPC->m_iLastFXTime <= cg.time )
{//until we attach it, we need to debounce this
vec3_t fwd, rt, up;
vec3_t flat;
float nextFXDelay = 50;
VectorSet(flat, 0, cent->lerpAngles[1], cent->lerpAngles[2]);
AngleVectors( flat, fwd, rt, up );
if ( cent->currentState.speed > 0 )
{//FIXME: only do this when accelerator is being pressed! (must have a driver?)
vec3_t org;
qboolean doExhaust = qfalse;
VectorMA( cent->lerpOrigin, -16, up, org );
VectorMA( org, -42, fwd, org );
// Play damage effects.
//if ( pVehNPC->m_iArmor <= 75 )
if (0)
{//hurt
trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 );
}
else if ( pVehNPC->m_pVehicleInfo->iTrailFX )
{//okay, do normal trail
trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, org, fwd, -1, -1 );
}
//=====================================================================
//EXHAUST FX
//=====================================================================
//do exhaust
if ( (cent->currentState.eFlags&EF_JETPACK_ACTIVE) )
{//cheap way of telling us the vehicle is in "turbo" mode
doExhaust = (pVehNPC->m_pVehicleInfo->iTurboFX!=0);
}
else
{
doExhaust = (pVehNPC->m_pVehicleInfo->iExhaustFX!=0);
}
if ( doExhaust && cent->ghoul2 )
{
int i;
int fx;
for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ )
{
// We hit an invalid tag, we quit (they should be created in order so tough luck if not).
if ( pVehNPC->m_iExhaustTag[i] == -1 )
{
break;
}
if ( (cent->currentState.brokenLimbs&(1<<SHIPSURF_DAMAGE_BACK_HEAVY)) )
{//engine has taken heavy damage
if ( !Q_irand( 0, 1 ) )
{//50% chance of not drawing this engine glow this frame
continue;
}
}
else if ( (cent->currentState.brokenLimbs&(1<<SHIPSURF_DAMAGE_BACK_LIGHT)) )
{//engine has taken light damage
if ( !Q_irand( 0, 4 ) )
{//20% chance of not drawing this engine glow this frame
continue;
}
}
if ( (cent->currentState.eFlags&EF_JETPACK_ACTIVE) //cheap way of telling us the vehicle is in "turbo" mode
&& pVehNPC->m_pVehicleInfo->iTurboFX )//they have a valid turbo exhaust effect to play
{
fx = pVehNPC->m_pVehicleInfo->iTurboFX;
}
else
{//play the normal one
fx = pVehNPC->m_pVehicleInfo->iExhaustFX;
}
if (pVehNPC->m_pVehicleInfo->type == VH_FIGHTER)
{
trap_FX_PlayBoltedEffectID(fx, cent->lerpOrigin, cent->ghoul2, pVehNPC->m_iExhaustTag[i],
cent->currentState.number, 0, 0, qtrue);
}
else
{ //fixme: bolt these too
mdxaBone_t boltMatrix;
vec3_t boltOrg, boltDir;
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iExhaustTag[i], &boltMatrix, flat,
cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg);
VectorCopy(fwd, boltDir); //fixme?
trap_FX_PlayEffectID( fx, boltOrg, boltDir, -1, -1 );
}
}
}
//=====================================================================
//WING TRAIL FX
//=====================================================================
//do trail
//FIXME: not in space!!!
if ( pVehNPC->m_pVehicleInfo->iTrailFX != 0 && cent->ghoul2 )
{
int i;
vec3_t boltOrg, boltDir;
mdxaBone_t boltMatrix;
vec3_t getBoltAngles;
VectorCopy(cent->lerpAngles, getBoltAngles);
if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER)
{ //only fighters use pitch/roll in refent axis
getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f;
}
for ( i = 1; i < 5; i++ )
{
int trailBolt = trap_G2API_AddBolt(cent->ghoul2, 0, va("*trail%d",i) );
// We hit an invalid tag, we quit (they should be created in order so tough luck if not).
if ( trailBolt == -1 )
{
break;
}
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, trailBolt, &boltMatrix, getBoltAngles,
cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg);
VectorCopy(fwd, boltDir); //fixme?
trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, boltOrg, boltDir, -1, -1 );
}
}
}
//FIXME armor needs to be sent over network
{
if ( (cent->currentState.eFlags&EF_DEAD) )
{//just plain dead, use flames
vec3_t up ={0,0,1};
vec3_t boltOrg;
//if ( pVehNPC->m_iDriverTag == -1 )
{//doh! no tag
VectorCopy( cent->lerpOrigin, boltOrg );
}
//else
//{
// mdxaBone_t boltMatrix;
// vec3_t getBoltAngles;
// VectorCopy(cent->lerpAngles, getBoltAngles);
// if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER)
// { //only fighters use pitch/roll in refent axis
// getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f;
// }
// trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iDriverTag, &boltMatrix, getBoltAngles,
// cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
// BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg);
//}
trap_FX_PlayEffectID( cgs.effects.mShipDestBurning, boltOrg, up, -1, -1 );
}
}
if ( cent->currentState.brokenLimbs )
{
int i;
if ( !Q_irand( 0, 5 ) )
{
for ( i = SHIPSURF_FRONT; i <= SHIPSURF_LEFT; i++ )
{
if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_HEAVY))) )
{//heavy damage, do both effects
if ( pVehNPC->m_pVehicleInfo->iInjureFX )
{
CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX );
}
if ( pVehNPC->m_pVehicleInfo->iDmgFX )
{
CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iDmgFX );
}
}
else if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_LIGHT))) )
{//only light damage
if ( pVehNPC->m_pVehicleInfo->iInjureFX )
{
CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX );
}
}
}
}
}
/*
if ( pVehNPC->m_iArmor <= 50 )
{//FIXME: use as a proportion of max armor?
VectorMA( cent->lerpOrigin, 64, fwd, org );
VectorScale( fwd, -1, fwd );
trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 );
}
if ( pVehNPC->m_iArmor <= 0 )
{//FIXME: should use something attached.. but want it to build up over time, so...
if ( flrand( 0, cg.time - pVehNPC->m_iDieTime ) < 1000 )
{//flaming!
VectorMA( cent->lerpOrigin, flrand(-64, 64), fwd, org );
VectorScale( fwd, -1, fwd );
trap_FX_PlayEffectID( trap_FX_RegisterEffect("ships/fire"), org, fwd, -1, -1 );
nextFXDelay = 50;
}
}
*/
pVehNPC->m_iLastFXTime = cg.time + nextFXDelay;
}
}
}
/*
===============
CG_Player
===============
*/
#include "../namespace_begin.h"
int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint);
#include "../namespace_end.h"
float CG_RadiusForCent( centity_t *cent )
{
if ( cent->currentState.eType == ET_NPC )
{
if (cent->currentState.NPC_class == CLASS_VEHICLE &&
cent->m_pVehicle &&
cent->m_pVehicle->m_pVehicleInfo->g2radius)
{ //has override
return cent->m_pVehicle->m_pVehicleInfo->g2radius;
}
else if ( cent->currentState.g2radius )
{
return cent->currentState.g2radius;
}
}
else if ( cent->currentState.g2radius )
{
return cent->currentState.g2radius;
}
return 64.0f;
}
static float cg_vehThirdPersonAlpha = 1.0f;
extern vec3_t cg_crosshairPos;
extern vec3_t cameraCurLoc;
void CG_CheckThirdPersonAlpha( centity_t *cent, refEntity_t *legs )
{
float alpha = 1.0f;
int setFlags = 0;
if ( cent->m_pVehicle )
{//a vehicle
if ( cg.predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum//not mine
&& cent->m_pVehicle->m_pVehicleInfo
&& cent->m_pVehicle->m_pVehicleInfo->cameraOverride
&& cent->m_pVehicle->m_pVehicleInfo->cameraAlpha )//it has alpha
{//make sure it's not using any alpha
legs->renderfx |= RF_FORCE_ENT_ALPHA;
legs->shaderRGBA[3] = 255;
return;
}
}
if ( !cg.renderingThirdPerson )
{
return;
}
if ( cg.predictedPlayerState.m_iVehicleNum )
{//in a vehicle
if ( cg.predictedPlayerState.m_iVehicleNum == cent->currentState.clientNum )
{//this is my vehicle
if ( cent->m_pVehicle
&& cent->m_pVehicle->m_pVehicleInfo
&& cent->m_pVehicle->m_pVehicleInfo->cameraOverride
&& cent->m_pVehicle->m_pVehicleInfo->cameraAlpha )
{//vehicle has auto third-person alpha on
trace_t trace;
vec3_t dir2Crosshair, end;
VectorSubtract( cg_crosshairPos, cameraCurLoc, dir2Crosshair );
VectorNormalize( dir2Crosshair );
VectorMA( cameraCurLoc, cent->m_pVehicle->m_pVehicleInfo->cameraRange*2.0f, dir2Crosshair, end );
CG_G2Trace( &trace, cameraCurLoc, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_BODY );
if ( trace.entityNum == cent->currentState.clientNum
|| trace.entityNum == cg.predictedPlayerState.clientNum)
{//hit me or the vehicle I'm in
cg_vehThirdPersonAlpha -= 0.1f*cg.frametime/50.0f;
if ( cg_vehThirdPersonAlpha < cent->m_pVehicle->m_pVehicleInfo->cameraAlpha )
{
cg_vehThirdPersonAlpha = cent->m_pVehicle->m_pVehicleInfo->cameraAlpha;
}
}
else
{
cg_vehThirdPersonAlpha += 0.1f*cg.frametime/50.0f;
if ( cg_vehThirdPersonAlpha > 1.0f )
{
cg_vehThirdPersonAlpha = 1.0f;
}
}
alpha = cg_vehThirdPersonAlpha;
}
else
{//use the cvar
//reset this
cg_vehThirdPersonAlpha = 1.0f;
//use the cvar
alpha = cg_thirdPersonAlpha.value;
}
}
}
else if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum )
{//it's me
//reset this
cg_vehThirdPersonAlpha = 1.0f;
//use the cvar
setFlags = RF_FORCE_ENT_ALPHA;
alpha = cg_thirdPersonAlpha.value;
}
if ( alpha < 1.0f )
{
legs->renderfx |= setFlags;
legs->shaderRGBA[3] = (unsigned char)(alpha * 255.0f);
}
}
void CG_Player( centity_t *cent ) {
clientInfo_t *ci;
refEntity_t legs;
refEntity_t torso;
int clientNum;
int renderfx;
qboolean shadow = qfalse;
float shadowPlane = 0;
qboolean dead = qfalse;
vec3_t rootAngles;
float angle;
vec3_t angles, dir, elevated, enang, seekorg;
int iwantout = 0, successchange = 0;
int team;
mdxaBone_t boltMatrix, lHandMatrix;
int doAlpha = 0;
qboolean gotLHandMatrix = qfalse;
qboolean g2HasWeapon = qfalse;
qboolean drawPlayerSaber = qfalse;
qboolean checkDroidShields = qfalse;
//first if we are not an npc and we are using an emplaced gun then make sure our
//angles are visually capped to the constraints (otherwise it's possible to lerp
//a little outside and look kind of twitchy)
if (cent->currentState.weapon == WP_EMPLACED_GUN &&
cent->currentState.otherEntityNum2)
{
float empYaw;
if (BG_EmplacedView(cent->lerpAngles, cg_entities[cent->currentState.otherEntityNum2].currentState.angles, &empYaw, cg_entities[cent->currentState.otherEntityNum2].currentState.origin2[0]))
{
cent->lerpAngles[YAW] = empYaw;
}
}
if (cent->currentState.iModelScale)
{ //if the server says we have a custom scale then set it now.
cent->modelScale[0] = cent->modelScale[1] = cent->modelScale[2] = cent->currentState.iModelScale/100.0f;
if ( cent->currentState.NPC_class != CLASS_VEHICLE )
{
if (cent->modelScale[2] && cent->modelScale[2] != 1.0f)
{
cent->lerpOrigin[2] += 24 * (cent->modelScale[2] - 1);
}
}
}
else
{
VectorClear(cent->modelScale);
}
if ((cg_smoothClients.integer || cent->currentState.heldByClient) && (cent->currentState.groundEntityNum >= ENTITYNUM_WORLD || cent->currentState.eType == ET_TERRAIN) &&
!(cent->currentState.eFlags2 & EF2_HYPERSPACE) && cg.predictedPlayerState.m_iVehicleNum != cent->currentState.number)
{ //always smooth when being thrown
vec3_t posDif;
float smoothFactor;
int k = 0;
float fTolerance = 20000.0f;
if (cent->currentState.heldByClient)
{ //smooth the origin more when in this state, because movement is origin-based on server.
smoothFactor = 0.2f;
}
else if ( (cent->currentState.powerups & (1 << PW_SPEED)) ||
(cent->currentState.forcePowersActive & (1 << FP_RAGE)) )
{ //we're moving fast so don't smooth as much
smoothFactor = 0.6f;
}
else if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE &&
cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
{ //greater smoothing for flying vehicles, since they move so fast
fTolerance = 6000000.0f;//500000.0f; //yeah, this is so wrong..but..
smoothFactor = 0.5f;
}
else
{
smoothFactor = 0.5f;
}
if (DistanceSquared(cent->beamEnd,cent->lerpOrigin) > smoothFactor*fTolerance) //10000
{
VectorCopy(cent->lerpOrigin, cent->beamEnd);
}
VectorSubtract(cent->lerpOrigin, cent->beamEnd, posDif);
for (k=0;k<3;k++)
{
cent->beamEnd[k]=(cent->beamEnd[k]+posDif[k]*smoothFactor);
cent->lerpOrigin[k]=cent->beamEnd[k];
}
}
else
{
VectorCopy(cent->lerpOrigin, cent->beamEnd);
}
if (cent->currentState.m_iVehicleNum &&
cent->currentState.NPC_class != CLASS_VEHICLE)
{ //this player is riding a vehicle
centity_t *veh = &cg_entities[cent->currentState.m_iVehicleNum];
cent->lerpAngles[YAW] = veh->lerpAngles[YAW];
//Attach ourself to the vehicle
if (veh->m_pVehicle &&
cent->playerState &&
veh->playerState &&
cent->ghoul2 &&
veh->ghoul2 )
{
if ( veh->currentState.owner != cent->currentState.clientNum )
{//FIXME: what about visible passengers?
if ( CG_VehicleAttachDroidUnit( cent, &legs ) )
{
checkDroidShields = qtrue;
}
}
else if ( veh->currentState.owner != ENTITYNUM_NONE)
{//has a pilot...???
vec3_t oldPSOrg;
//make sure it has its pilot and parent set
veh->m_pVehicle->m_pPilot = (bgEntity_t *)&cg_entities[veh->currentState.owner];
veh->m_pVehicle->m_pParentEntity = (bgEntity_t *)veh;
VectorCopy(veh->playerState->origin, oldPSOrg);
//update the veh's playerstate org for getting the bolt
VectorCopy(veh->lerpOrigin, veh->playerState->origin);
VectorCopy(cent->lerpOrigin, cent->playerState->origin);
//Now do the attach
VectorCopy(veh->lerpAngles, veh->playerState->viewangles);
veh->m_pVehicle->m_pVehicleInfo->AttachRiders(veh->m_pVehicle);
//copy the "playerstate origin" to the lerpOrigin since that's what we use to display
VectorCopy(cent->playerState->origin, cent->lerpOrigin);
VectorCopy(oldPSOrg, veh->playerState->origin);
}
}
}
// the client number is stored in clientNum. It can't be derived
// from the entity number, because a single client may have
// multiple corpses on the level using the same clientinfo
if (cent->currentState.eType != ET_NPC)
{
clientNum = cent->currentState.clientNum;
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
CG_Error( "Bad clientNum on player entity");
}
ci = &cgs.clientinfo[ clientNum ];
}
else
{
if (!cent->npcClient)
{
CG_CreateNPCClient(&cent->npcClient); //allocate memory for it
if (!cent->npcClient)
{
assert(0);
return;
}
memset(cent->npcClient, 0, sizeof(clientInfo_t));
cent->npcClient->ghoul2Model = NULL;
}
assert(cent->npcClient);
if (cent->npcClient->ghoul2Model != cent->ghoul2 && cent->ghoul2)
{
cent->npcClient->ghoul2Model = cent->ghoul2;
if (cent->localAnimIndex <= 1)
{
cent->npcClient->bolt_rhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand");
cent->npcClient->bolt_lhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand");
//rhand must always be first bolt. lhand always second. Whichever you want the
//jetpack bolted to must always be third.
trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*chestg");
//claw bolts
trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand_cap_r_arm");
trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand_cap_l_arm");
cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*head_top");
if (cent->npcClient->bolt_head == -1)
{
cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "ceyebrow");
}
cent->npcClient->bolt_motion = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "Motion");
cent->npcClient->bolt_llumbar = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "lower_lumbar");
}
else
{
cent->npcClient->bolt_rhand = -1;
cent->npcClient->bolt_lhand = -1;
cent->npcClient->bolt_head = -1;
cent->npcClient->bolt_motion = -1;
cent->npcClient->bolt_llumbar = -1;
}
cent->npcClient->team = TEAM_FREE;
cent->npcClient->infoValid = qtrue;
}
ci = cent->npcClient;
}
// it is possible to see corpses from disconnected players that may
// not have valid clientinfo
if ( !ci->infoValid ) {
return;
}
// Add the player to the radar if on the same team and its a team game
if (cgs.gametype >= GT_TEAM)
{
if ( cent->currentState.eType != ET_NPC &&
cg.snap->ps.clientNum != cent->currentState.number &&
ci->team == cg.snap->ps.persistant[PERS_TEAM] )
{
CG_AddRadarEnt(cent);
}
}
if (cent->currentState.eType == ET_NPC &&
cent->currentState.NPC_class == CLASS_VEHICLE)
{ //add vehicles
CG_AddRadarEnt(cent);
if ( CG_InFighter() )
{//this is a vehicle, bracket it
if ( cg.predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum )
{//don't add the vehicle I'm in... :)
CG_AddBracketedEnt(cent);
}
}
}
if (!cent->ghoul2)
{ //not ready yet?
#ifdef _DEBUG
Com_Printf("WARNING: Client %i has a null ghoul2 instance\n", cent->currentState.number);
#endif
trap_G2API_ClearAttachedInstance(cent->currentState.number);
if (ci->ghoul2Model &&
trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{
#ifdef _DEBUG
Com_Printf("Clientinfo instance was valid, duplicating for cent\n");
#endif
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cent->ghoul2);
//Attach the instance to this entity num so we can make use of client-server
//shared operations if possible.
trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse);
if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1)
{ //check now to see if we have this bone for setting anims and such
cent->noFace = qtrue;
}
cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2);
cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex);
}
return;
}
if (ci->superSmoothTime)
{ //do crazy smoothing
if (ci->superSmoothTime > cg.time)
{ //do it
trap_G2API_AbsurdSmoothing(cent->ghoul2, qtrue);
}
else
{ //turn it off
ci->superSmoothTime = 0;
trap_G2API_AbsurdSmoothing(cent->ghoul2, qfalse);
}
}
if (cg.predictedPlayerState.pm_type == PM_INTERMISSION)
{ //don't show all this shit during intermission
if ( cent->currentState.eType == ET_NPC
&& cent->currentState.NPC_class != CLASS_VEHICLE )
{//NPC in intermission
}
else
{//don't render players or vehicles in intermissions, allow other NPCs for scripts
return;
}
}
CG_VehicleEffects(cent);
if ((cent->currentState.eFlags & EF_JETPACK) && !(cent->currentState.eFlags & EF_DEAD) &&
cg_g2JetpackInstance)
{ //should have a jetpack attached
//1 is rhand weap, 2 is lhand weap (akimbo sabs), 3 is jetpack
if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3))
{
trap_G2API_CopySpecificGhoul2Model(cg_g2JetpackInstance, 0, cent->ghoul2, 3);
}
if (cent->currentState.eFlags & EF_JETPACK_ACTIVE)
{
mdxaBone_t mat;
vec3_t flamePos, flameDir;
int n = 0;
while (n < 2)
{
//Get the position/dir of the flame bolt on the jetpack model bolted to the player
trap_G2API_GetBoltMatrix(cent->ghoul2, 3, n, &mat, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&mat, ORIGIN, flamePos);
if (n == 0)
{
BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir);
VectorMA(flamePos, -9.5f, flameDir, flamePos);
BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir);
VectorMA(flamePos, -13.5f, flameDir, flamePos);
}
else
{
BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir);
VectorMA(flamePos, -9.5f, flameDir, flamePos);
BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir);
VectorMA(flamePos, -13.5f, flameDir, flamePos);
}
if (cent->currentState.eFlags & EF_JETPACK_FLAMING)
{ //create effects
//FIXME: Just one big effect
//Play the effect
trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1);
trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1);
//Keep the jet fire sound looping
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
trap_S_RegisterSound( "sound/effects/fire_lp" ) );
}
else
{ //just idling
//FIXME: Different smaller effect for idle
//Play the effect
trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1);
}
n++;
}
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
trap_S_RegisterSound( "sound/boba/JETHOVER" ) );
}
}
else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3))
{ //fixme: would be good if this could be done not every frame
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 3);
}
g2HasWeapon = trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1);
if (!g2HasWeapon)
{ //force a redup of the weapon instance onto the client instance
cent->ghoul2weapon = NULL;
cent->weapon = 0;
}
if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD))
{ //he's alive and has a limb missing still, reattach it and reset the weapon
CG_ReattachLimb(cent);
}
if (cent->isRagging && !(cent->currentState.eFlags & EF_DEAD) && !(cent->currentState.eFlags & EF_RAG))
{ //make sure we don't ragdoll ever while alive unless directly told to with eFlags
cent->isRagging = qfalse;
trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll.
}
if (cent->ghoul2 && cent->torsoBolt && ((cent->torsoBolt & RARMBIT) || (cent->torsoBolt & RHANDBIT) || (cent->torsoBolt & WAISTBIT)) && g2HasWeapon)
{ //kill the weapon if the limb holding it is no longer on the model
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
g2HasWeapon = qfalse;
}
if (!cent->trickAlphaTime || (cg.time - cent->trickAlphaTime) > 1000)
{ //things got out of sync, perhaps a new client is trying to fill in this slot
cent->trickAlpha = 255;
cent->trickAlphaTime = cg.time;
}
if (cent->currentState.eFlags & EF_NODRAW)
{ //If nodraw, return here
return;
}
else if (cent->currentState.eFlags2 & EF2_SHIP_DEATH)
{ //died in ship, don't draw, we were "obliterated"
return;
}
//If this client has tricked you.
if (CG_IsMindTricked(cent->currentState.trickedentindex,
cent->currentState.trickedentindex2,
cent->currentState.trickedentindex3,
cent->currentState.trickedentindex4,
cg.snap->ps.clientNum))
{
if (cent->trickAlpha > 1)
{
cent->trickAlpha -= (cg.time - cent->trickAlphaTime)*0.5;
cent->trickAlphaTime = cg.time;
if (cent->trickAlpha < 0)
{
cent->trickAlpha = 0;
}
doAlpha = 1;
}
else
{
doAlpha = 1;
cent->trickAlpha = 1;
cent->trickAlphaTime = cg.time;
iwantout = 1;
}
}
else
{
if (cent->trickAlpha < 255)
{
cent->trickAlpha += (cg.time - cent->trickAlphaTime);
cent->trickAlphaTime = cg.time;
if (cent->trickAlpha > 255)
{
cent->trickAlpha = 255;
}
doAlpha = 1;
}
else
{
cent->trickAlpha = 255;
cent->trickAlphaTime = cg.time;
}
}
// get the player model information
renderfx = 0;
if ( cent->currentState.number == cg.snap->ps.clientNum) {
if (!cg.renderingThirdPerson) {
#if 0
if (!cg_fpls.integer || cent->currentState.weapon != WP_SABER)
#else
if (cent->currentState.weapon != WP_SABER)
#endif
{
renderfx = RF_THIRD_PERSON; // only draw in mirrors
}
} else {
if (cg_cameraMode.integer) {
iwantout = 1;
// goto minimal_add;
// NOTENOTE Temporary
return;
}
}
}
// Update the player's client entity information regarding weapons.
// Explanation: The entitystate has a weapond defined on it. The cliententity does as well.
// The cliententity's weapon tells us what the ghoul2 instance on the cliententity has bolted to it.
// If the entitystate and cliententity weapons differ, then the state's needs to be copied to the client.
// Save the old weapon, to verify that it is or is not the same as the new weapon.
// rww - Make sure weapons don't get set BEFORE cent->ghoul2 is initialized or else we'll have no
// weapon bolted on
if (cent->currentState.saberInFlight)
{
cent->ghoul2weapon = CG_G2WeaponInstance(cent, WP_SABER);
}
if (cent->ghoul2 &&
(cent->currentState.eType != ET_NPC || (cent->currentState.NPC_class != CLASS_VEHICLE&&cent->currentState.NPC_class != CLASS_REMOTE&&cent->currentState.NPC_class != CLASS_SEEKER)) && //don't add weapon models to NPCs that have no bolt for them!
cent->ghoul2weapon != CG_G2WeaponInstance(cent, cent->currentState.weapon) &&
!(cent->currentState.eFlags & EF_DEAD) && !cent->torsoBolt &&
cg.snap && (cent->currentState.number != cg.snap->ps.clientNum || (cg.snap->ps.pm_flags & PMF_FOLLOW)))
{
if (ci->team == TEAM_SPECTATOR)
{
cent->ghoul2weapon = NULL;
cent->weapon = 0;
}
else
{
CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2);
if (cent->currentState.eType != ET_NPC)
{
if (cent->weapon == WP_SABER
&& cent->weapon != cent->currentState.weapon
&& !cent->currentState.saberHolstered)
{ //switching away from the saber
//trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" ));
if (ci->saber[0].soundOff
&& !cent->currentState.saberHolstered)
{
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOff);
}
if (ci->saber[1].soundOff &&
ci->saber[1].model[0] &&
!cent->currentState.saberHolstered)
{
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOff);
}
}
else if (cent->currentState.weapon == WP_SABER
&& cent->weapon != cent->currentState.weapon
&& !cent->saberWasInFlight)
{ //switching to the saber
//trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ));
if (ci->saber[0].soundOn)
{
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOn);
}
if (ci->saber[1].soundOn)
{
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOn);
}
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
}
}
cent->weapon = cent->currentState.weapon;
cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon);
}
}
else if ((cent->currentState.eFlags & EF_DEAD) || cent->torsoBolt)
{
cent->ghoul2weapon = NULL; //be sure to update after respawning/getting limb regrown
}
if (cent->saberWasInFlight && g2HasWeapon)
{
cent->saberWasInFlight = qfalse;
}
memset (&legs, 0, sizeof(legs));
CG_SetGhoul2Info(&legs, cent);
VectorCopy(cent->modelScale, legs.modelScale);
legs.radius = CG_RadiusForCent( cent );
VectorClear(legs.angles);
if (ci->colorOverride[0] != 0.0f ||
ci->colorOverride[1] != 0.0f ||
ci->colorOverride[2] != 0.0f)
{
legs.shaderRGBA[0] = ci->colorOverride[0]*255.0f;
legs.shaderRGBA[1] = ci->colorOverride[1]*255.0f;
legs.shaderRGBA[2] = ci->colorOverride[2]*255.0f;
legs.shaderRGBA[3] = cent->currentState.customRGBA[3];
}
else
{
legs.shaderRGBA[0] = cent->currentState.customRGBA[0];
legs.shaderRGBA[1] = cent->currentState.customRGBA[1];
legs.shaderRGBA[2] = cent->currentState.customRGBA[2];
legs.shaderRGBA[3] = cent->currentState.customRGBA[3];
}
// minimal_add:
team = ci->team;
if (cgs.gametype >= GT_TEAM && cg_drawFriend.integer &&
cent->currentState.number != cg.snap->ps.clientNum &&
cent->currentState.eType != ET_NPC)
{ // If the view is either a spectator or on the same team as this character, show a symbol above their head.
if ((cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == team) &&
!(cent->currentState.eFlags & EF_DEAD))
{
if (cgs.gametype == GT_SIEGE)
{ //check for per-map team shaders
if (team == SIEGETEAM_TEAM1)
{
if (cgSiegeTeam1PlShader)
{
CG_PlayerFloatSprite( cent, cgSiegeTeam1PlShader);
}
else
{ //if there isn't one fallback to default
CG_PlayerFloatSprite( cent, cgs.media.teamRedShader);
}
}
else
{
if (cgSiegeTeam2PlShader)
{
CG_PlayerFloatSprite( cent, cgSiegeTeam2PlShader);
}
else
{ //if there isn't one fallback to default
CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader);
}
}
}
else
{ //generic teamplay
if (team == TEAM_RED)
{
CG_PlayerFloatSprite( cent, cgs.media.teamRedShader);
}
else // if (team == TEAM_BLUE)
{
CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader);
}
}
}
}
else if (cgs.gametype == GT_POWERDUEL && cg_drawFriend.integer &&
cent->currentState.number != cg.snap->ps.clientNum)
{
if (cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR &&
cent->currentState.number < MAX_CLIENTS &&
!(cent->currentState.eFlags & EF_DEAD) &&
ci &&
cgs.clientinfo[cg.snap->ps.clientNum].duelTeam == ci->duelTeam)
{ //ally in powerduel, so draw the icon
CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader);
}
else if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR &&
cent->currentState.number < MAX_CLIENTS &&
!(cent->currentState.eFlags & EF_DEAD) &&
ci->duelTeam == DUELTEAM_DOUBLE)
{
CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader);
}
}
if (cgs.gametype == GT_JEDIMASTER && cg_drawFriend.integer &&
cent->currentState.number != cg.snap->ps.clientNum) // Don't show a sprite above a player's own head in 3rd person.
{ // If the view is either a spectator or on the same team as this character, show a symbol above their head.
if ((cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == team) &&
!(cent->currentState.eFlags & EF_DEAD))
{
if (CG_ThereIsAMaster())
{
if (!cg.snap->ps.isJediMaster)
{
if (!cent->currentState.isJediMaster)
{
CG_PlayerFloatSprite( cent, cgs.media.teamRedShader);
}
}
}
}
}
// add the shadow
shadow = CG_PlayerShadow( cent, &shadowPlane );
if ( ((cent->currentState.eFlags & EF_SEEKERDRONE) || cent->currentState.genericenemyindex != -1) && cent->currentState.eType != ET_NPC )
{
refEntity_t seeker;
memset( &seeker, 0, sizeof(seeker) );
VectorCopy(cent->lerpOrigin, elevated);
elevated[2] += 40;
VectorCopy( elevated, seeker.lightingOrigin );
seeker.shadowPlane = shadowPlane;
seeker.renderfx = 0; //renderfx;
//don't show in first person?
angle = ((cg.time / 12) & 255) * (M_PI * 2) / 255;
dir[0] = cos(angle) * 20;
dir[1] = sin(angle) * 20;
dir[2] = cos(angle) * 5;
VectorAdd(elevated, dir, seeker.origin);
VectorCopy(seeker.origin, seekorg);
if (cent->currentState.genericenemyindex > MAX_GENTITIES)
{
float prefig = (cent->currentState.genericenemyindex-cg.time)/80;
if (prefig > 55)
{
prefig = 55;
}
else if (prefig < 1)
{
prefig = 1;
}
elevated[2] -= 55-prefig;
angle = ((cg.time / 12) & 255) * (M_PI * 2) / 255;
dir[0] = cos(angle) * 20;
dir[1] = sin(angle) * 20;
dir[2] = cos(angle) * 5;
VectorAdd(elevated, dir, seeker.origin);
}
else if (cent->currentState.genericenemyindex != ENTITYNUM_NONE && cent->currentState.genericenemyindex != -1)
{
centity_t *enent = &cg_entities[cent->currentState.genericenemyindex];
if (enent)
{
VectorSubtract(enent->lerpOrigin, seekorg, enang);
VectorNormalize(enang);
vectoangles(enang, angles);
successchange = 1;
}
}
if (!successchange)
{
angles[0] = sin(angle) * 30;
angles[1] = (angle * 180 / M_PI) + 90;
if (angles[1] > 360)
angles[1] -= 360;
angles[2] = 0;
}
AnglesToAxis( angles, seeker.axis );
seeker.hModel = trap_R_RegisterModel("models/items/remote.md3");
trap_R_AddRefEntityToScene( &seeker );
}
// add a water splash if partially in and out of water
CG_PlayerSplash( cent );
if ( (cg_shadows.integer == 3 || cg_shadows.integer == 2) && shadow ) {
renderfx |= RF_SHADOW_PLANE;
}
renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
// if we've been hit, display proper fullscreen fx
CG_PlayerHitFX(cent);
VectorCopy( cent->lerpOrigin, legs.origin );
VectorCopy( cent->lerpOrigin, legs.lightingOrigin );
legs.shadowPlane = shadowPlane;
legs.renderfx = renderfx;
if (cg_shadows.integer == 2 && (renderfx & RF_THIRD_PERSON))
{ //can see own shadow
legs.renderfx |= RF_SHADOW_ONLY;
}
VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all
CG_G2PlayerAngles( cent, legs.axis, rootAngles );
CG_G2PlayerHeadAnims( cent );
if ( (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER)
&& cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor...
{
centity_t *rancor = &cg_entities[cent->currentState.lookTarget];
if ( rancor )
{
BG_AttachToRancor( rancor->ghoul2, //ghoul2 info
rancor->lerpAngles[YAW],
rancor->lerpOrigin,
cg.time,
cgs.gameModels,
rancor->modelScale,
(rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG),
legs.origin,
legs.angles,
NULL );
if ( cent->isRagging )
{//hack, ragdoll has you way at bottom of bounding box
VectorMA( legs.origin, 32, legs.axis[2], legs.origin );
}
VectorCopy( legs.origin, legs.oldorigin );
VectorCopy( legs.origin, legs.lightingOrigin );
VectorCopy( legs.angles, cent->lerpAngles );
VectorCopy( cent->lerpAngles, rootAngles );//??? tempAngles );//tempAngles is needed a lot below
VectorCopy( cent->lerpAngles, cent->turAngles );
VectorCopy( legs.origin, cent->lerpOrigin );
}
}
//This call is mainly just to reconstruct the skeleton. But we'll get the left hand matrix while we're at it.
//If we don't reconstruct the skeleton after setting the bone angles, we will get bad bolt points on the model
//(e.g. the weapon model bolt will look "lagged") if there's no other GetBoltMatrix call for the rest of the
//frame. Yes, this is stupid and needs to be fixed properly.
//The current solution is to force it not to reconstruct the skeleton for the first GBM call in G2PlayerAngles.
//It works and we end up only reconstructing it once, but it doesn't seem like the best solution.
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
gotLHandMatrix = qtrue;
#if 0
if (cg.renderingThirdPerson)
{
if (cgFPLSState != 0)
{
CG_ForceFPLSPlayerModel(cent, ci);
cgFPLSState = 0;
return;
}
}
else if (ci->team == TEAM_SPECTATOR || (cg.snap && (cg.snap->ps.pm_flags & PMF_FOLLOW)))
{ //don't allow this when spectating
if (cgFPLSState != 0)
{
trap_Cvar_Set("cg_fpls", "0");
cg_fpls.integer = 0;
CG_ForceFPLSPlayerModel(cent, ci);
cgFPLSState = 0;
return;
}
if (cg_fpls.integer)
{
trap_Cvar_Set("cg_fpls", "0");
}
}
else
{
if (cg_fpls.integer && cent->currentState.weapon == WP_SABER && cg.snap && cent->currentState.number == cg.snap->ps.clientNum)
{
if (cgFPLSState != cg_fpls.integer)
{
CG_ForceFPLSPlayerModel(cent, ci);
cgFPLSState = cg_fpls.integer;
return;
}
/*
mdxaBone_t headMatrix;
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &headMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&headMatrix, ORIGIN, cg.refdef.vieworg);
*/
}
else if (!cg_fpls.integer && cgFPLSState)
{
if (cgFPLSState != cg_fpls.integer)
{
CG_ForceFPLSPlayerModel(cent, ci);
cgFPLSState = cg_fpls.integer;
return;
}
}
}
#endif
if (cent->currentState.eFlags & EF_DEAD)
{
dead = qtrue;
//rww - since our angles are fixed when we're dead this shouldn't be an issue anyway
//we need to render the dying/dead player because we are now spawning the body on respawn instead of death
//return;
}
ScaleModelAxis(&legs);
memset( &torso, 0, sizeof(torso) );
//rww - force speed "trail" effect
if (!(cent->currentState.powerups & (1 << PW_SPEED)) || doAlpha || !cg_speedTrail.integer)
{
cent->frame_minus1_refreshed = 0;
cent->frame_minus2_refreshed = 0;
}
if (cent->frame_minus1_refreshed ||
cent->frame_minus2_refreshed)
{
vec3_t tDir;
int distVelBase;
VectorCopy(cent->currentState.pos.trDelta, tDir);
distVelBase = SPEED_TRAIL_DISTANCE*(VectorNormalize(tDir)*0.004);
if (cent->frame_minus1_refreshed)
{
refEntity_t reframe_minus1 = legs;
reframe_minus1.renderfx |= RF_FORCE_ENT_ALPHA;
reframe_minus1.shaderRGBA[0] = legs.shaderRGBA[0];
reframe_minus1.shaderRGBA[1] = legs.shaderRGBA[1];
reframe_minus1.shaderRGBA[2] = legs.shaderRGBA[2];
reframe_minus1.shaderRGBA[3] = 100;
//rww - if the client gets a bad framerate we will only receive frame positions
//once per frame anyway, so we might end up with speed trails very spread out.
//in order to avoid that, we'll get the direction of the last trail from the player
//and place the trail refent a set distance from the player location this frame
VectorSubtract(cent->frame_minus1, legs.origin, tDir);
VectorNormalize(tDir);
cent->frame_minus1[0] = legs.origin[0]+tDir[0]*distVelBase;
cent->frame_minus1[1] = legs.origin[1]+tDir[1]*distVelBase;
cent->frame_minus1[2] = legs.origin[2]+tDir[2]*distVelBase;
VectorCopy(cent->frame_minus1, reframe_minus1.origin);
//reframe_minus1.customShader = 2;
trap_R_AddRefEntityToScene(&reframe_minus1);
}
if (cent->frame_minus2_refreshed)
{
refEntity_t reframe_minus2 = legs;
reframe_minus2.renderfx |= RF_FORCE_ENT_ALPHA;
reframe_minus2.shaderRGBA[0] = legs.shaderRGBA[0];
reframe_minus2.shaderRGBA[1] = legs.shaderRGBA[1];
reframe_minus2.shaderRGBA[2] = legs.shaderRGBA[2];
reframe_minus2.shaderRGBA[3] = 50;
//Same as above but do it between trail points instead of the player and first trail entry
VectorSubtract(cent->frame_minus2, cent->frame_minus1, tDir);
VectorNormalize(tDir);
cent->frame_minus2[0] = cent->frame_minus1[0]+tDir[0]*distVelBase;
cent->frame_minus2[1] = cent->frame_minus1[1]+tDir[1]*distVelBase;
cent->frame_minus2[2] = cent->frame_minus1[2]+tDir[2]*distVelBase;
VectorCopy(cent->frame_minus2, reframe_minus2.origin);
//reframe_minus2.customShader = 2;
trap_R_AddRefEntityToScene(&reframe_minus2);
}
}
//trigger animation-based sounds, done before next lerp frame.
CG_TriggerAnimSounds(cent);
// get the animation state (after rotation, to allow feet shuffle)
CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp,
&torso.oldframe, &torso.frame, &torso.backlerp );
// add the talk baloon or disconnect icon
CG_PlayerSprites( cent );
if (cent->currentState.eFlags & EF_DEAD)
{ //keep track of death anim frame for when we copy off the bodyqueue
ci->frame = cent->pe.torso.frame;
}
if (cent->currentState.activeForcePass > FORCE_LEVEL_3
&& cent->currentState.NPC_class != CLASS_VEHICLE)
{
vec3_t axis[3];
vec3_t tAng, fAng, fxDir;
vec3_t efOrg;
int realForceLev = (cent->currentState.activeForcePass - FORCE_LEVEL_3);
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 );
AngleVectors( fAng, fxDir, NULL, NULL );
if ( cent->currentState.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
&& Q_irand( 0, 1 ) )
{//alternate back and forth between left and right
mdxaBone_t rHandMatrix;
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &rHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
efOrg[0] = rHandMatrix.matrix[0][3];
efOrg[1] = rHandMatrix.matrix[1][3];
efOrg[2] = rHandMatrix.matrix[2][3];
}
else
{
//trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
if (!gotLHandMatrix)
{
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
gotLHandMatrix = qtrue;
}
efOrg[0] = lHandMatrix.matrix[0][3];
efOrg[1] = lHandMatrix.matrix[1][3];
efOrg[2] = lHandMatrix.matrix[2][3];
}
AnglesToAxis( fAng, axis );
if ( realForceLev > FORCE_LEVEL_2 )
{//arc
//trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir );
//trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1);
trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, -1, -1, -1, -1);
}
else
{//line
//trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir );
//trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1);
trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, -1, -1, -1, -1);
}
/*
if (cent->bolt4 < cg.time)
{
cent->bolt4 = cg.time + 100;
trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/drain.wav") );
}
*/
}
else if ( cent->currentState.activeForcePass
&& cent->currentState.NPC_class != CLASS_VEHICLE)
{//doing the electrocuting
vec3_t axis[3];
vec3_t tAng, fAng, fxDir;
vec3_t efOrg;
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 );
AngleVectors( fAng, fxDir, NULL, NULL );
//trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
if (!gotLHandMatrix)
{
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
gotLHandMatrix = qtrue;
}
efOrg[0] = lHandMatrix.matrix[0][3];
efOrg[1] = lHandMatrix.matrix[1][3];
efOrg[2] = lHandMatrix.matrix[2][3];
AnglesToAxis( fAng, axis );
if ( cent->currentState.activeForcePass > FORCE_LEVEL_2 )
{//arc
//trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir );
//trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1);
trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, -1, -1, -1, -1);
}
else
{//line
//trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir );
//trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1);
trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, -1, -1, -1, -1);
}
/*
if (cent->bolt4 < cg.time)
{
cent->bolt4 = cg.time + 100;
trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/lightning.wav") );
}
*/
}
//fullbody push effect
if (cent->currentState.eFlags & EF_BODYPUSH)
{
CG_ForcePushBodyBlur(cent);
}
if ( cent->currentState.powerups & (1 << PW_DISINT_4) )
{
vec3_t tAng;
vec3_t efOrg;
//VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 );
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
//trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
if (!gotLHandMatrix)
{
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
gotLHandMatrix = qtrue;
}
efOrg[0] = lHandMatrix.matrix[0][3];
efOrg[1] = lHandMatrix.matrix[1][3];
efOrg[2] = lHandMatrix.matrix[2][3];
if ( (cent->currentState.forcePowersActive & (1 << FP_GRIP)) &&
(cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum) )
{
vec3_t boltDir;
vec3_t origBolt;
VectorCopy(efOrg, origBolt);
BG_GiveMeVectorFromMatrix( &lHandMatrix, NEGATIVE_Y, boltDir );
CG_ForceGripEffect( efOrg );
CG_ForceGripEffect( efOrg );
/*
//Render a scaled version of the model's hand with a n337 looking shader
{
const char *rotateBone;
char *limbName;
char *limbCapName;
vec3_t armAng;
refEntity_t regrip_arm;
float wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f;
//rotateBone = "lradius";
rotateBone = "lradiusX";
limbName = "l_arm";
limbCapName = "l_arm_cap_torso";
if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm))
{
trap_G2API_CleanGhoul2Models(&(cent->grip_arm));
}
memset( &regrip_arm, 0, sizeof(regrip_arm) );
VectorCopy(origBolt, efOrg);
//efOrg[2] += 8;
efOrg[2] -= 4;
VectorCopy(efOrg, regrip_arm.origin);
VectorCopy(regrip_arm.origin, regrip_arm.lightingOrigin);
//VectorCopy(cent->lerpAngles, armAng);
VectorAdd(vec3_origin, rootAngles, armAng);
//armAng[ROLL] = -90;
armAng[ROLL] = 0;
armAng[PITCH] = 0;
AnglesToAxis(armAng, regrip_arm.axis);
trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &cent->grip_arm);
//remove all other models
if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 1))
{ //weapon right
trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 1);
}
if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 2))
{ //weapon left
trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 2);
}
if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 3))
{ //jetpack
trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 3);
}
trap_G2API_SetRootSurface(cent->grip_arm, 0, limbName);
trap_G2API_SetNewOrigin(cent->grip_arm, trap_G2API_AddBolt(cent->grip_arm, 0, rotateBone));
trap_G2API_SetSurfaceOnOff(cent->grip_arm, limbCapName, 0);
regrip_arm.modelScale[0] = 1;//+(wv*6);
regrip_arm.modelScale[1] = 1;//+(wv*6);
regrip_arm.modelScale[2] = 1;//+(wv*6);
ScaleModelAxis(&regrip_arm);
regrip_arm.radius = 64;
regrip_arm.customShader = trap_R_RegisterShader( "gfx/misc/red_portashield" );
regrip_arm.renderfx |= RF_RGB_TINT;
regrip_arm.shaderRGBA[0] = 255 - (wv*900);
if (regrip_arm.shaderRGBA[0] < 30)
{
regrip_arm.shaderRGBA[0] = 30;
}
if (regrip_arm.shaderRGBA[0] > 255)
{
regrip_arm.shaderRGBA[0] = 255;
}
regrip_arm.shaderRGBA[1] = regrip_arm.shaderRGBA[2] = regrip_arm.shaderRGBA[0];
regrip_arm.ghoul2 = cent->grip_arm;
trap_R_AddRefEntityToScene( &regrip_arm );
}
*/
}
else if (!(cent->currentState.forcePowersActive & (1 << FP_GRIP)))
{
//use refractive effect
CG_ForcePushBlur( efOrg, cent );
}
}
else if (cent->bodyFadeTime)
{ //reset the counter for keeping track of push refraction effect state
cent->bodyFadeTime = 0;
}
if (cent->currentState.weapon == WP_STUN_BATON && cent->currentState.number == cg.snap->ps.clientNum)
{
trap_S_AddLoopingSound( cent->currentState.number, cg.refdef.vieworg, vec3_origin,
trap_S_RegisterSound( "sound/weapons/baton/idle.wav" ) );
}
//NOTE: All effects that should be visible during mindtrick should go above here
if (iwantout)
{
goto stillDoSaber;
//return;
}
else if (doAlpha)
{
legs.renderfx |= RF_FORCE_ENT_ALPHA;
legs.shaderRGBA[3] = cent->trickAlpha;
if (legs.shaderRGBA[3] < 1)
{ //don't cancel it out even if it's < 1
legs.shaderRGBA[3] = 1;
}
}
if (cent->teamPowerEffectTime > cg.time)
{
if (cent->teamPowerType == 3)
{ //absorb is a somewhat different effect entirely
//Guess I'll take care of it where it's always been, just checking these values instead.
}
else
{
vec4_t preCol;
int preRFX;
preRFX = legs.renderfx;
legs.renderfx |= RF_RGB_TINT;
legs.renderfx |= RF_FORCE_ENT_ALPHA;
preCol[0] = legs.shaderRGBA[0];
preCol[1] = legs.shaderRGBA[1];
preCol[2] = legs.shaderRGBA[2];
preCol[3] = legs.shaderRGBA[3];
if (cent->teamPowerType == 1)
{ //heal
legs.shaderRGBA[0] = 0;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 0;
}
else if (cent->teamPowerType == 0)
{ //regen
legs.shaderRGBA[0] = 0;
legs.shaderRGBA[1] = 0;
legs.shaderRGBA[2] = 255;
}
else
{ //drain
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 0;
legs.shaderRGBA[2] = 0;
}
legs.shaderRGBA[3] = ((cent->teamPowerEffectTime - cg.time)/8);
legs.customShader = trap_R_RegisterShader( "powerups/ysalimarishell" );
trap_R_AddRefEntityToScene(&legs);
legs.customShader = 0;
legs.renderfx = preRFX;
legs.shaderRGBA[0] = preCol[0];
legs.shaderRGBA[1] = preCol[1];
legs.shaderRGBA[2] = preCol[2];
legs.shaderRGBA[3] = preCol[3];
}
}
//If you've tricked this client.
if (CG_IsMindTricked(cg.snap->ps.fd.forceMindtrickTargetIndex,
cg.snap->ps.fd.forceMindtrickTargetIndex2,
cg.snap->ps.fd.forceMindtrickTargetIndex3,
cg.snap->ps.fd.forceMindtrickTargetIndex4,
cent->currentState.number))
{
if (cent->ghoul2)
{
vec3_t efOrg;
vec3_t tAng, fxAng;
vec3_t axis[3];
//VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 );
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, efOrg);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fxAng);
axis[0][0] = boltMatrix.matrix[0][0];
axis[0][1] = boltMatrix.matrix[1][0];
axis[0][2] = boltMatrix.matrix[2][0];
axis[1][0] = boltMatrix.matrix[0][1];
axis[1][1] = boltMatrix.matrix[1][1];
axis[1][2] = boltMatrix.matrix[2][1];
axis[2][0] = boltMatrix.matrix[0][2];
axis[2][1] = boltMatrix.matrix[1][2];
axis[2][2] = boltMatrix.matrix[2][2];
//trap_FX_PlayEntityEffectID(trap_FX_RegisterEffect("force/confusion.efx"), efOrg, axis, cent->boltInfo, cent->currentState.number);
trap_FX_PlayEntityEffectID(cgs.effects.mForceConfustionOld, efOrg, axis, -1, -1, -1, -1);
}
}
if (cgs.gametype == GT_HOLOCRON && cent->currentState.time2 && (cg.renderingThirdPerson || cg.snap->ps.clientNum != cent->currentState.number))
{
int i = 0;
int renderedHolos = 0;
refEntity_t holoRef;
while (i < NUM_FORCE_POWERS && renderedHolos < 3)
{
if (cent->currentState.time2 & (1 << i))
{
memset( &holoRef, 0, sizeof(holoRef) );
VectorCopy(cent->lerpOrigin, elevated);
elevated[2] += 8;
VectorCopy( elevated, holoRef.lightingOrigin );
holoRef.shadowPlane = shadowPlane;
holoRef.renderfx = 0;//RF_THIRD_PERSON;
if (renderedHolos == 0)
{
angle = ((cg.time / 8) & 255) * (M_PI * 2) / 255;
dir[0] = cos(angle) * 20;
dir[1] = sin(angle) * 20;
dir[2] = cos(angle) * 20;
VectorAdd(elevated, dir, holoRef.origin);
angles[0] = sin(angle) * 30;
angles[1] = (angle * 180 / M_PI) + 90;
if (angles[1] > 360)
angles[1] -= 360;
angles[2] = 0;
AnglesToAxis( angles, holoRef.axis );
}
else if (renderedHolos == 1)
{
angle = ((cg.time / 8) & 255) * (M_PI * 2) / 255 + M_PI;
if (angle > M_PI * 2)
angle -= (float)M_PI * 2;
dir[0] = sin(angle) * 20;
dir[1] = cos(angle) * 20;
dir[2] = cos(angle) * 20;
VectorAdd(elevated, dir, holoRef.origin);
angles[0] = cos(angle - 0.5 * M_PI) * 30;
angles[1] = 360 - (angle * 180 / M_PI);
if (angles[1] > 360)
angles[1] -= 360;
angles[2] = 0;
AnglesToAxis( angles, holoRef.axis );
}
else
{
angle = ((cg.time / 6) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI;
if (angle > M_PI * 2)
angle -= (float)M_PI * 2;
dir[0] = sin(angle) * 20;
dir[1] = cos(angle) * 20;
dir[2] = 0;
VectorAdd(elevated, dir, holoRef.origin);
VectorCopy(dir, holoRef.axis[1]);
VectorNormalize(holoRef.axis[1]);
VectorSet(holoRef.axis[2], 0, 0, 1);
CrossProduct(holoRef.axis[1], holoRef.axis[2], holoRef.axis[0]);
}
holoRef.modelScale[0] = 0.5;
holoRef.modelScale[1] = 0.5;
holoRef.modelScale[2] = 0.5;
ScaleModelAxis(&holoRef);
{
float wv;
addspriteArgStruct_t fxSArgs;
vec3_t holoCenter;
holoCenter[0] = holoRef.origin[0] + holoRef.axis[2][0]*18;
holoCenter[1] = holoRef.origin[1] + holoRef.axis[2][1]*18;
holoCenter[2] = holoRef.origin[2] + holoRef.axis[2][2]*18;
wv = sin( cg.time * 0.004f ) * 0.08f + 0.1f;
VectorCopy(holoCenter, fxSArgs.origin);
VectorClear(fxSArgs.vel);
VectorClear(fxSArgs.accel);
fxSArgs.scale = wv*60;
fxSArgs.dscale = wv*60;
fxSArgs.sAlpha = wv*12;
fxSArgs.eAlpha = wv*12;
fxSArgs.rotation = 0.0f;
fxSArgs.bounce = 0.0f;
fxSArgs.life = 1.0f;
fxSArgs.flags = 0x08000000|0x00000001;
if (forcePowerDarkLight[i] == FORCE_DARKSIDE)
{ //dark
fxSArgs.sAlpha *= 3;
fxSArgs.eAlpha *= 3;
fxSArgs.shader = cgs.media.redSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
}
else if (forcePowerDarkLight[i] == FORCE_LIGHTSIDE)
{ //light
fxSArgs.sAlpha *= 1.5;
fxSArgs.eAlpha *= 1.5;
fxSArgs.shader = cgs.media.redSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
fxSArgs.shader = cgs.media.greenSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
fxSArgs.shader = cgs.media.blueSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
}
else
{ //neutral
if (i == FP_SABER_OFFENSE ||
i == FP_SABER_DEFENSE ||
i == FP_SABERTHROW)
{ //saber power
fxSArgs.sAlpha *= 1.5;
fxSArgs.eAlpha *= 1.5;
fxSArgs.shader = cgs.media.greenSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
}
else
{
fxSArgs.sAlpha *= 0.5;
fxSArgs.eAlpha *= 0.5;
fxSArgs.shader = cgs.media.greenSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
fxSArgs.shader = cgs.media.blueSaberGlowShader;
trap_FX_AddSprite(&fxSArgs);
}
}
}
holoRef.hModel = trap_R_RegisterModel(forceHolocronModels[i]);
trap_R_AddRefEntityToScene( &holoRef );
renderedHolos++;
}
i++;
}
}
if ((cent->currentState.powerups & (1 << PW_YSALAMIRI)) ||
(cgs.gametype == GT_CTY && ((cent->currentState.powerups & (1 << PW_REDFLAG)) || (cent->currentState.powerups & (1 << PW_BLUEFLAG)))) )
{
if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_REDFLAG)))
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliredShader );
}
else if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_BLUEFLAG)))
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliblueShader );
}
else
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysalimariShader );
}
}
if (cent->currentState.powerups & (1 << PW_FORCE_BOON))
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.boonShader );
}
if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_DARK))
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.endarkenmentShader );
}
else if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_LIGHT))
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.enlightenmentShader );
}
if (cent->currentState.eFlags & EF_INVULNERABLE)
{
CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.0f, cgs.media.invulnerabilityShader );
}
stillDoSaber:
if ((cent->currentState.eFlags & EF_DEAD) && cent->currentState.weapon == WP_SABER)
{
//cent->saberLength = 0;
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
drawPlayerSaber = qtrue;
}
else if (cent->currentState.weapon == WP_SABER
&& cent->currentState.saberHolstered < 2 )
{
if ( (!cent->currentState.saberInFlight //saber not in flight
|| ci->saber[1].soundLoop) //???
&& !(cent->currentState.eFlags & EF_DEAD))//still alive
{
vec3_t soundSpot;
qboolean didFirstSound = qfalse;
if (cg.snap->ps.clientNum == cent->currentState.number)
{
//trap_S_AddLoopingSound( cent->currentState.number, cg.refdef.vieworg, vec3_origin,
// trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) );
VectorCopy(cg.refdef.vieworg, soundSpot);
}
else
{
//trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
// trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) );
VectorCopy(cent->lerpOrigin, soundSpot);
}
if (ci->saber[0].model[0]
&& ci->saber[0].soundLoop
&& !cent->currentState.saberInFlight)
{
int i = 0;
qboolean hasLen = qfalse;
while (i < ci->saber[0].numBlades)
{
if (ci->saber[0].blade[i].length)
{
hasLen = qtrue;
break;
}
i++;
}
if (hasLen)
{
trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin,
ci->saber[0].soundLoop );
didFirstSound = qtrue;
}
}
if (ci->saber[1].model[0]
&& ci->saber[1].soundLoop
&& (!didFirstSound || ci->saber[0].soundLoop != ci->saber[1].soundLoop))
{
int i = 0;
qboolean hasLen = qfalse;
while (i < ci->saber[1].numBlades)
{
if (ci->saber[1].blade[i].length)
{
hasLen = qtrue;
break;
}
i++;
}
if (hasLen)
{
trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin,
ci->saber[1].soundLoop );
}
}
}
if (iwantout
&& !cent->currentState.saberInFlight)
{
if (cent->currentState.eFlags & EF_DEAD)
{
if (cent->ghoul2
&& cent->currentState.saberInFlight
&& g2HasWeapon)
{ //special case, kill the saber on a freshly dead player if another source says to.
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
g2HasWeapon = qfalse;
}
}
return;
//goto endOfCall;
}
if (g2HasWeapon
&& cent->currentState.saberInFlight)
{ //keep this set, so we don't re-unholster the thing when we get it back, even if it's knocked away.
cent->saberWasInFlight = qtrue;
}
if (cent->currentState.saberInFlight
&& cent->currentState.saberEntityNum)
{
centity_t *saberEnt;
saberEnt = &cg_entities[cent->currentState.saberEntityNum];
if (/*!cent->bolt4 &&*/ g2HasWeapon || !cent->bolt3 ||
saberEnt->serverSaberHitIndex != saberEnt->currentState.modelindex/*|| !cent->saberLength*/)
{ //saber is in flight, do not have it as a standard weapon model
qboolean addBolts = qfalse;
mdxaBone_t boltMat;
if (g2HasWeapon)
{
//ah well, just stick it over the right hand right now.
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMat, cent->turAngles, cent->lerpOrigin,
cg.time, cgs.gameModels, cent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMat, ORIGIN, saberEnt->currentState.pos.trBase);
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
g2HasWeapon = qfalse;
}
//cent->bolt4 = 1;
saberEnt->currentState.pos.trTime = cg.time;
saberEnt->currentState.apos.trTime = cg.time;
VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin);
VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles);
cent->bolt3 = saberEnt->currentState.apos.trBase[0];
if (!cent->bolt3)
{
cent->bolt3 = 1;
}
cent->bolt2 = 0;
saberEnt->currentState.bolt2 = 123;
if (saberEnt->ghoul2 &&
saberEnt->serverSaberHitIndex == saberEnt->currentState.modelindex)
{
// now set up the gun bolt on it
addBolts = qtrue;
}
else
{
const char *saberModel = CG_ConfigString( CS_MODELS+saberEnt->currentState.modelindex );
saberEnt->serverSaberHitIndex = saberEnt->currentState.modelindex;
if (saberEnt->ghoul2)
{ //clean if we already have one (because server changed model string index)
trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2));
saberEnt->ghoul2 = 0;
}
if (saberModel && saberModel[0])
{
trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, saberModel, 0, 0, 0, 0, 0);
}
else if (ci->saber[0].model[0])
{
trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, ci->saber[0].model, 0, 0, 0, 0, 0);
}
else
{
trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0);
}
//trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &saberEnt->ghoul2);
if (saberEnt->ghoul2)
{
addBolts = qtrue;
//cent->bolt4 = 2;
VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin);
VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles);
saberEnt->currentState.pos.trTime = cg.time;
saberEnt->currentState.apos.trTime = cg.time;
}
}
if (addBolts)
{
int m = 0;
int tagBolt;
char *tagName;
while (m < ci->saber[0].numBlades)
{
tagName = va("*blade%i", m+1);
tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, tagName);
if (tagBolt == -1)
{
if (m == 0)
{ //guess this is an 0ldsk3wl saber
tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash");
if (tagBolt == -1)
{
assert(0);
}
break;
}
if (tagBolt == -1)
{
assert(0);
break;
}
}
m++;
}
}
}
/*else if (cent->bolt4 != 2)
{
if (saberEnt->ghoul2)
{
trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash");
cent->bolt4 = 2;
}
}*/
if (saberEnt && saberEnt->ghoul2 /*&& cent->bolt4 == 2*/)
{
vec3_t bladeAngles;
vec3_t tAng;
vec3_t efOrg;
float wv;
int k = 0;
int l = 0;
addspriteArgStruct_t fxSArgs;
if (!cent->bolt2)
{
cent->bolt2 = cg.time;
}
if (cent->bolt3 != 90)
{
if (cent->bolt3 < 90)
{
cent->bolt3 += (cg.time - cent->bolt2)*0.5;
if (cent->bolt3 > 90)
{
cent->bolt3 = 90;
}
}
else if (cent->bolt3 > 90)
{
cent->bolt3 -= (cg.time - cent->bolt2)*0.5;
if (cent->bolt3 < 90)
{
cent->bolt3 = 90;
}
}
}
cent->bolt2 = cg.time;
saberEnt->currentState.apos.trBase[0] = cent->bolt3;
saberEnt->lerpAngles[0] = cent->bolt3;
if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123)
{ //owner is pulling is back
if ( !(ci->saber[0].saberFlags&SFL_RETURN_DAMAGE)
|| cent->currentState.saberHolstered )
{
vec3_t owndir;
VectorSubtract(saberEnt->lerpOrigin, cent->lerpOrigin, owndir);
VectorNormalize(owndir);
vectoangles(owndir, owndir);
owndir[0] += 90;
VectorCopy(owndir, saberEnt->currentState.apos.trBase);
VectorCopy(owndir, saberEnt->lerpAngles);
VectorClear(saberEnt->currentState.apos.trDelta);
}
}
//We don't actually want to rely entirely on server updates to render the position of the saber, because we actually know generally where
//it's going to be before the first position update even gets here, and it needs to start getting rendered the instant the saber model is
//removed from the player hand. So we'll just render it manually and let normal rendering for the entity be ignored.
if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123)
{ //tell it that we're a saber and to render the glow around our handle because we're being pulled back
saberEnt->bolt3 = 999;
}
saberEnt->currentState.modelGhoul2 = 1;
CG_ManualEntityRender(saberEnt);
saberEnt->bolt3 = 0;
saberEnt->currentState.modelGhoul2 = 127;
VectorCopy(saberEnt->lerpAngles, bladeAngles);
bladeAngles[ROLL] = 0;
if ( ci->saber[0].numBlades > 1//staff
&& cent->currentState.saberHolstered == 1 )//extra blades off
{//only first blade should be on
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[0], -1, 0);
}
else
{
BG_SI_SetDesiredLength(&ci->saber[0], -1, -1);
}
if ( ci->saber[1].model //dual sabers
&& cent->currentState.saberHolstered == 1 )//second one off
{
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
}
else
{
BG_SI_SetDesiredLength(&ci->saber[1], -1, -1);
}
//while (l < MAX_SABERS)
//Only want to do for the first saber actually, it's the one in flight.
while (l < 1)
{
if (!ci->saber[l].model[0])
{
break;
}
k = 0;
while (k < ci->saber[l].numBlades)
{
if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style
l == 0//first saber
&& cent->currentState.saberHolstered == 1 //extra blades should be off
&& k > 0 )//this is an extra blade
{//extra blades off
//don't draw them
CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qtrue);
}
else
{
CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qfalse);
}
k++;
}
if ( ci->saber[l].numBlades > 2 )
{//add a single glow for the saber based on all the blade colors combined
CG_DoSaberLight( &ci->saber[l] );
}
l++;
}
//Make the player's hand glow while guiding the saber
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
efOrg[0] = boltMatrix.matrix[0][3];
efOrg[1] = boltMatrix.matrix[1][3];
efOrg[2] = boltMatrix.matrix[2][3];
wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f;
//trap_FX_AddSprite( NULL, efOrg, NULL, NULL, 8.0f, 8.0f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 );
VectorCopy(efOrg, fxSArgs.origin);
VectorClear(fxSArgs.vel);
VectorClear(fxSArgs.accel);
fxSArgs.scale = 8.0f;
fxSArgs.dscale = 8.0f;
fxSArgs.sAlpha = wv;
fxSArgs.eAlpha = wv;
fxSArgs.rotation = 0.0f;
fxSArgs.bounce = 0.0f;
fxSArgs.life = 1.0f;
fxSArgs.shader = cgs.media.yellowDroppedSaberShader;
fxSArgs.flags = 0x08000000;
trap_FX_AddSprite(&fxSArgs);
}
}
else
{
if ( ci->saber[0].numBlades > 1//staff
&& cent->currentState.saberHolstered == 1 )//extra blades off
{//only first blade should be on
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[0], -1, 0);
}
else
{
BG_SI_SetDesiredLength(&ci->saber[0], -1, -1);
}
if ( ci->saber[1].model //dual sabers
&& cent->currentState.saberHolstered == 1 )//second one off
{
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
}
else
{
BG_SI_SetDesiredLength(&ci->saber[1], -1, -1);
}
}
//If the arm the saber is in is broken, turn it off.
/*
if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM))
{
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
}
*/
//Leaving right arm on, at least for now.
if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM))
{
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
}
if (!cent->currentState.saberEntityNum)
{
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
//BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
}
/*
else
{
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
}
*/
drawPlayerSaber = qtrue;
}
else if (cent->currentState.weapon == WP_SABER)
{
//cent->saberLength = 0;
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
drawPlayerSaber = qtrue;
}
else
{
//cent->saberLength = 0;
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
BG_SI_SetLength(&ci->saber[0], 0);
BG_SI_SetLength(&ci->saber[1], 0);
}
#ifdef _RAG_BOLT_TESTING
if (cent->currentState.eFlags & EF_RAG)
{
CG_TempTestFunction(cent, cent->turAngles);
}
#endif
if (cent->currentState.weapon == WP_SABER)
{
BG_SI_SetLengthGradual(&ci->saber[0], cg.time);
BG_SI_SetLengthGradual(&ci->saber[1], cg.time);
}
if (drawPlayerSaber)
{
centity_t *saberEnt;
int k = 0;
int l = 0;
if (!cent->currentState.saberEntityNum)
{
l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one
}
else if (!cent->currentState.saberInFlight)
{
saberEnt = &cg_entities[cent->currentState.saberEntityNum];
if (/*cent->bolt4 && */!g2HasWeapon)
{
trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, WP_SABER), 0, cent->ghoul2, 1);
if (saberEnt && saberEnt->ghoul2)
{
trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2));
}
saberEnt->currentState.modelindex = 0;
saberEnt->ghoul2 = NULL;
VectorClear(saberEnt->currentState.pos.trBase);
}
cent->bolt3 = 0;
cent->bolt2 = 0;
}
else
{
l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one
}
while (l < MAX_SABERS)
{
k = 0;
if (!ci->saber[l].model[0])
{
break;
}
if (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER)
{
//vectoangles(legs.axis[0], rootAngles);
#if 0
if ( cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor...
{
centity_t *rancor = &cg_entities[cent->currentState.lookTarget];
if ( rancor && rancor->ghoul2 )
{
BG_AttachToRancor( rancor->ghoul2, //ghoul2 info
rancor->lerpAngles[YAW],
rancor->lerpOrigin,
cg.time,
cgs.gameModels,
rancor->modelScale,
(rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG),
legs.origin,
rootAngles,
NULL );
}
}
#else
vectoangles(legs.axis[0], rootAngles);
#endif
}
while (k < ci->saber[l].numBlades)
{
if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style
cent->currentState.saberHolstered == 1 //extra blades should be off
&& k > 0 //this is an extra blade
&& ci->saber[l].blade[k].length <= 0 )//it's completely off
{//extra blades off
//don't draw them
CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue);
}
else if ( ci->saber[1].model[0]//we have a second saber
&& cent->currentState.saberHolstered == 1 //it should be off
&& l > 0//and this is the second one
&& ci->saber[l].blade[k].length <= 0 )//it's completely off
{//second saber is turned off and this blade is done with turning off
CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue);
}
else
{
CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qfalse);
}
k++;
}
if ( ci->saber[l].numBlades > 2 )
{//add a single glow for the saber based on all the blade colors combined
CG_DoSaberLight( &ci->saber[l] );
}
l++;
}
}
if (cent->currentState.saberInFlight && !cent->currentState.saberEntityNum)
{ //reset the length if the saber is knocked away
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
if (g2HasWeapon)
{ //and remember to kill the bolton model in case we didn't get a thrown saber update first
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
g2HasWeapon = qfalse;
}
cent->bolt3 = 0;
cent->bolt2 = 0;
}
if (cent->currentState.eFlags & EF_DEAD)
{
if (cent->ghoul2 && cent->currentState.saberInFlight && g2HasWeapon)
{ //special case, kill the saber on a freshly dead player if another source says to.
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
g2HasWeapon = qfalse;
}
}
if (iwantout)
{
return;
//goto endOfCall;
}
if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number)
{
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 0;
legs.renderfx |= RF_MINLIGHT;
}
if (cg.snap->ps.duelInProgress /*&& cent->currentState.number != cg.snap->ps.clientNum*/)
{ //I guess go ahead and glow your own client too in a duel
if (cent->currentState.number != cg.snap->ps.duelIndex &&
cent->currentState.number != cg.snap->ps.clientNum)
{ //everyone not involved in the duel is drawn very dark
legs.shaderRGBA[0] /= 5.0f;
legs.shaderRGBA[1] /= 5.0f;
legs.shaderRGBA[2] /= 5.0f;
legs.renderfx |= RF_RGB_TINT;
}
else
{ //adjust the glow by how far away you are from your dueling partner
centity_t *duelEnt;
duelEnt = &cg_entities[cg.snap->ps.duelIndex];
if (duelEnt)
{
vec3_t vecSub;
float subLen = 0;
VectorSubtract(duelEnt->lerpOrigin, cg.snap->ps.origin, vecSub);
subLen = VectorLength(vecSub);
if (subLen < 1)
{
subLen = 1;
}
if (subLen > 1020)
{
subLen = 1020;
}
{
const unsigned char savRGBA[3] = {legs.shaderRGBA[0],legs.shaderRGBA[1],legs.shaderRGBA[2]};
legs.shaderRGBA[0] = max(255-subLen/4,1);
legs.shaderRGBA[1] = max(255-subLen/4,1);
legs.shaderRGBA[2] = max(255-subLen/4,1);
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.customShader = cgs.media.forceShell;
trap_R_AddRefEntityToScene( &legs ); //draw the shell
legs.customShader = 0; //reset to player model
legs.shaderRGBA[0] = max(savRGBA[0]-subLen/8,1);
legs.shaderRGBA[1] = max(savRGBA[1]-subLen/8,1);
legs.shaderRGBA[2] = max(savRGBA[2]-subLen/8,1);
}
if (subLen <= 1024)
{
legs.renderfx |= RF_RGB_TINT;
}
}
}
}
else
{
if (cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg.snap->ps.clientNum && (!cg.snap->ps.duelInProgress || cg.snap->ps.duelIndex != cent->currentState.number))
{
legs.shaderRGBA[0] = 50;
legs.shaderRGBA[1] = 50;
legs.shaderRGBA[2] = 50;
legs.renderfx |= RF_RGB_TINT;
}
}
if (cent->currentState.eFlags & EF_DISINTEGRATION)
{
if (!cent->dustTrailTime)
{
cent->dustTrailTime = cg.time;
cent->miscTime = legs.frame;
}
if ((cg.time - cent->dustTrailTime) > 1500)
{ //avoid rendering the entity after disintegration has finished anyway
//goto endOfCall;
return;
}
trap_G2API_SetBoneAnim(legs.ghoul2, 0, "model_root", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1);
if (!cent->noLumbar)
{
trap_G2API_SetBoneAnim(legs.ghoul2, 0, "lower_lumbar", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1);
if (cent->localAnimIndex <= 1)
{
trap_G2API_SetBoneAnim(legs.ghoul2, 0, "Motion", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1);
}
}
CG_Disintegration(cent, &legs);
//goto endOfCall;
return;
}
else
{
cent->dustTrailTime = 0;
cent->miscTime = 0;
}
if (cent->currentState.powerups & (1 << PW_CLOAKED))
{
if (!cent->cloaked)
{
cent->cloaked = qtrue;
cent->uncloaking = cg.time + 2000;
}
}
else if (cent->cloaked)
{
cent->cloaked = qfalse;
cent->uncloaking = cg.time + 2000;
}
if (cent->uncloaking > cg.time)
{//in the middle of cloaking
if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE))
&& cg.snap->ps.clientNum != cent->currentState.number)
{//just draw him
trap_R_AddRefEntityToScene( &legs );
}
else
{
float perc = (float)(cent->uncloaking - cg.time) / 2000.0f;
if (( cent->currentState.powerups & ( 1 << PW_CLOAKED )))
{//actually cloaking, so reverse it
perc = 1.0f - perc;
}
if ( perc >= 0.0f && perc <= 1.0f )
{
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.renderfx |= RF_RGB_TINT;
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * perc;
legs.shaderRGBA[3] = 0;
legs.customShader = cgs.media.cloakedShader;
trap_R_AddRefEntityToScene( &legs );
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255;
legs.shaderRGBA[3] = 255 * (1.0f - perc); // let model alpha in
legs.customShader = 0; // use regular skin
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx |= RF_FORCE_ENT_ALPHA;
trap_R_AddRefEntityToScene( &legs );
}
}
}
else if (( cent->currentState.powerups & ( 1 << PW_CLOAKED )))
{//fully cloaked
if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE))
&& cg.snap->ps.clientNum != cent->currentState.number)
{//just draw him
trap_R_AddRefEntityToScene( &legs );
}
else
{
if (cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum)
{
/*
legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE);
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255;
legs.customShader = cgs.media.cloakedShader;
legs.nonNormalizedAxes = qtrue;
legs.modelScale[0] = 1.02f;
legs.modelScale[1] = 1.02f;
legs.modelScale[2] = 1.02f;
VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] );
VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] );
VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] );
ScaleModelAxis(&legs);
trap_R_AddRefEntityToScene( &legs );
legs.modelScale[0] = 0.98f;
legs.modelScale[1] = 0.98f;
legs.modelScale[2] = 0.98f;
VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] );
VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] );
VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] );
ScaleModelAxis(&legs);
*/
if (cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4 && cg_renderToTextureFX.integer)
{
trap_R_SetRefractProp(1.0f, 0.0f, qfalse, qfalse); //don't need to do this every frame.. but..
legs.customShader = 2; //crazy "refractive" shader
trap_R_AddRefEntityToScene( &legs );
legs.customShader = 0;
}
else
{ //stencil buffer's in use, sorry
legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE);
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255;
legs.customShader = cgs.media.cloakedShader;
trap_R_AddRefEntityToScene( &legs );
legs.customShader = 0;
}
}
}
}
if (!(cent->currentState.powerups & (1 << PW_CLOAKED)))
{ //don't add the normal model if cloaked
CG_CheckThirdPersonAlpha( cent, &legs );
trap_R_AddRefEntityToScene(&legs);
}
//cent->frame_minus2 = cent->frame_minus1;
VectorCopy(cent->frame_minus1, cent->frame_minus2);
if (cent->frame_minus1_refreshed)
{
cent->frame_minus2_refreshed = 1;
}
//cent->frame_minus1 = legs;
VectorCopy(legs.origin, cent->frame_minus1);
cent->frame_minus1_refreshed = 1;
if (!cent->frame_hold_refreshed && (cent->currentState.powerups & (1 << PW_SPEEDBURST)))
{
cent->frame_hold_time = cg.time + 254;
}
if (cent->frame_hold_time >= cg.time)
{
refEntity_t reframe_hold;
if (!cent->frame_hold_refreshed)
{ //We're taking the ghoul2 instance from the original refent and duplicating it onto our refent alias so that we can then freeze the frame and fade it for the effect
if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold) &&
cent->frame_hold != cent->ghoul2)
{
trap_G2API_CleanGhoul2Models(&(cent->frame_hold));
}
reframe_hold = legs;
cent->frame_hold_refreshed = 1;
reframe_hold.ghoul2 = NULL;
trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &cent->frame_hold);
//Set the animation to the current frame and freeze on end
//trap_G2API_SetBoneAnim(cent->frame_hold.ghoul2, 0, "model_root", cent->frame_hold.frame, cent->frame_hold.frame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->frame_hold.frame, -1);
trap_G2API_SetBoneAnim(cent->frame_hold, 0, "model_root", legs.frame, legs.frame, 0, 1.0f, cg.time, legs.frame, -1);
}
else
{
reframe_hold = legs;
reframe_hold.ghoul2 = cent->frame_hold;
}
reframe_hold.renderfx |= RF_FORCE_ENT_ALPHA;
reframe_hold.shaderRGBA[3] = (cent->frame_hold_time - cg.time);
if (reframe_hold.shaderRGBA[3] > 254)
{
reframe_hold.shaderRGBA[3] = 254;
}
if (reframe_hold.shaderRGBA[3] < 1)
{
reframe_hold.shaderRGBA[3] = 1;
}
reframe_hold.ghoul2 = cent->frame_hold;
trap_R_AddRefEntityToScene(&reframe_hold);
}
else
{
cent->frame_hold_refreshed = 0;
}
//
// add the gun / barrel / flash
//
if (cent->currentState.weapon != WP_EMPLACED_GUN)
{
CG_AddPlayerWeapon( &legs, NULL, cent, ci->team, rootAngles, qtrue );
}
// add powerups floating behind the player
CG_PlayerPowerups( cent, &legs );
if ((cent->currentState.forcePowersActive & (1 << FP_RAGE)) &&
(cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum))
{
//legs.customShader = cgs.media.rageShader;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.renderfx &= ~RF_MINLIGHT;
legs.renderfx |= RF_RGB_TINT;
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = legs.shaderRGBA[2] = 0;
legs.shaderRGBA[3] = 255;
if ( rand() & 1 )
{
legs.customShader = cgs.media.electricBodyShader;
}
else
{
legs.customShader = cgs.media.electricBody2Shader;
}
trap_R_AddRefEntityToScene(&legs);
}
if (!cg.snap->ps.duelInProgress && cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg.snap->ps.clientNum && (!cg.snap->ps.duelInProgress || cg.snap->ps.duelIndex != cent->currentState.number))
{
legs.shaderRGBA[0] = 50;
legs.shaderRGBA[1] = 50;
legs.shaderRGBA[2] = 255;
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.customShader = cgs.media.forceSightBubble;
trap_R_AddRefEntityToScene( &legs );
}
if ( CG_VehicleShouldDrawShields( cent ) //vehicle
|| (checkDroidShields && CG_VehicleShouldDrawShields( &cg_entities[cent->currentState.m_iVehicleNum] )) )//droid in vehicle
{//Vehicles have form-fitting shields
Vehicle_t *pVeh = cent->m_pVehicle;
if ( checkDroidShields )
{
pVeh = cg_entities[cent->currentState.m_iVehicleNum].m_pVehicle;
}
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 255;
legs.shaderRGBA[3] = 10.0f+(sin((float)(cg.time/4))*128.0f);//112.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + random()*16;
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
if ( pVeh
&& pVeh->m_pVehicleInfo
&& pVeh->m_pVehicleInfo->shieldShaderHandle )
{//use the vehicle-specific shader
legs.customShader = pVeh->m_pVehicleInfo->shieldShaderHandle;
}
else
{
legs.customShader = cgs.media.playerShieldDamage;
}
trap_R_AddRefEntityToScene( &legs );
}
//For now, these two are using the old shield shader. This is just so that you
//can tell it apart from the JM/duel shaders, but it's still very obvious.
if (cent->currentState.forcePowersActive & (1 << FP_PROTECT))
{ //aborb is represented by green..
refEntity_t prot;
memcpy(&prot, &legs, sizeof(prot));
prot.shaderRGBA[0] = 0;
prot.shaderRGBA[1] = 128;
prot.shaderRGBA[2] = 0;
prot.shaderRGBA[3] = 254;
prot.renderfx &= ~RF_RGB_TINT;
prot.renderfx &= ~RF_FORCE_ENT_ALPHA;
prot.customShader = cgs.media.protectShader;
/*
if (!prot.modelScale[0] && !prot.modelScale[1] && !prot.modelScale[2])
{
prot.modelScale[0] = prot.modelScale[1] = prot.modelScale[2] = 1.0f;
}
VectorScale(prot.modelScale, 1.1f, prot.modelScale);
prot.origin[2] -= 2.0f;
ScaleModelAxis(&prot);
*/
trap_R_AddRefEntityToScene( &prot );
}
//if (cent->currentState.forcePowersActive & (1 << FP_ABSORB))
//Showing only when the power has been active (absorbed something) recently now, instead of always.
//AND
//always show if it is you with the absorb on
if ((cent->currentState.number == cg.predictedPlayerState.clientNum && (cg.predictedPlayerState.fd.forcePowersActive & (1<<FP_ABSORB))) ||
(cent->teamPowerEffectTime > cg.time && cent->teamPowerType == 3))
{ //aborb is represented by blue..
legs.shaderRGBA[0] = 0;
legs.shaderRGBA[1] = 0;
legs.shaderRGBA[2] = 255;
legs.shaderRGBA[3] = 254;
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.customShader = cgs.media.playerShieldDamage;
trap_R_AddRefEntityToScene( &legs );
}
if (cent->currentState.isJediMaster && cg.snap->ps.clientNum != cent->currentState.number)
{
legs.shaderRGBA[0] = 100;
legs.shaderRGBA[1] = 100;
legs.shaderRGBA[2] = 255;
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.renderfx |= RF_NODEPTH;
legs.customShader = cgs.media.forceShell;
trap_R_AddRefEntityToScene( &legs );
legs.renderfx &= ~RF_NODEPTH;
}
if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number && cg_auraShell.integer)
{
if (cgs.gametype == GT_SIEGE)
{ // A team game
if ( ci->team == TEAM_SPECTATOR || ci->team == TEAM_FREE )
{//yellow
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 0;
}
else if ( ci->team != cgs.clientinfo[cg.snap->ps.clientNum].team )
{//red
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 50;
legs.shaderRGBA[2] = 50;
}
else
{//green
legs.shaderRGBA[0] = 50;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 50;
}
}
else if (cgs.gametype >= GT_TEAM)
{ // A team game
switch(ci->team)
{
case TEAM_RED:
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 50;
legs.shaderRGBA[2] = 50;
break;
case TEAM_BLUE:
legs.shaderRGBA[0] = 75;
legs.shaderRGBA[1] = 75;
legs.shaderRGBA[2] = 255;
break;
default:
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 0;
break;
}
}
else
{ // Not a team game
legs.shaderRGBA[0] = 255;
legs.shaderRGBA[1] = 255;
legs.shaderRGBA[2] = 0;
}
/* if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] <= FORCE_LEVEL_1)
{
legs.renderfx |= RF_MINLIGHT;
}
else
*/ { // See through walls.
legs.renderfx |= RF_MINLIGHT | RF_NODEPTH;
if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2)
{ //only level 2+ can see players through walls
legs.renderfx &= ~RF_NODEPTH;
}
}
legs.renderfx &= ~RF_RGB_TINT;
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.customShader = cgs.media.sightShell;
trap_R_AddRefEntityToScene( &legs );
}
// Electricity
//------------------------------------------------
if ( cent->currentState.emplacedOwner > cg.time )
{
int dif = cent->currentState.emplacedOwner - cg.time;
vec3_t tempAngles;
if ( dif > 0 && random() > 0.4f )
{
// fade out over the last 500 ms
int brightness = 255;
if ( dif < 500 )
{
brightness = floor((dif - 500.0f) / 500.0f * 255.0f );
}
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.renderfx &= ~RF_MINLIGHT;
legs.renderfx |= RF_RGB_TINT;
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = brightness;
legs.shaderRGBA[3] = 255;
if ( rand() & 1 )
{
legs.customShader = cgs.media.electricBodyShader;
}
else
{
legs.customShader = cgs.media.electricBody2Shader;
}
trap_R_AddRefEntityToScene( &legs );
if ( random() > 0.9f )
trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.crackleSound );
}
VectorSet(tempAngles, 0, cent->lerpAngles[YAW], 0);
CG_ForceElectrocution( cent, legs.origin, tempAngles, cgs.media.boltShader, qfalse );
}
if (cent->currentState.powerups & (1 << PW_SHIELDHIT))
{
/*
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * 0.5f;//t;
legs.shaderRGBA[3] = 255;
legs.renderfx &= ~RF_ALPHA_FADE;
legs.renderfx |= RF_RGB_TINT;
*/
legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = Q_irand(1, 255);
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
legs.renderfx &= ~RF_MINLIGHT;
legs.renderfx &= ~RF_RGB_TINT;
legs.customShader = cgs.media.playerShieldDamage;
trap_R_AddRefEntityToScene( &legs );
}
#if 0
endOfCall:
if (cgBoneAnglePostSet.refreshSet)
{
trap_G2API_SetBoneAngles(cgBoneAnglePostSet.ghoul2, cgBoneAnglePostSet.modelIndex, cgBoneAnglePostSet.boneName,
cgBoneAnglePostSet.angles, cgBoneAnglePostSet.flags, cgBoneAnglePostSet.up, cgBoneAnglePostSet.right,
cgBoneAnglePostSet.forward, cgBoneAnglePostSet.modelList, cgBoneAnglePostSet.blendTime, cgBoneAnglePostSet.currentTime);
cgBoneAnglePostSet.refreshSet = qfalse;
}
#endif
}
//=====================================================================
/*
===============
CG_ResetPlayerEntity
A player just came into view or teleported, so reset all animation info
===============
*/
void CG_ResetPlayerEntity( centity_t *cent )
{
clientInfo_t *ci;
int i = 0;
int j = 0;
// cent->errorTime = -99999; // guarantee no error decay added
// cent->extrapolated = qfalse;
if (cent->currentState.eType == ET_NPC)
{
if (cent->currentState.NPC_class == CLASS_VEHICLE &&
cent->m_pVehicle &&
cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER &&
cg.predictedPlayerState.m_iVehicleNum &&
cent->currentState.number == cg.predictedPlayerState.m_iVehicleNum)
{ //holy hackery, batman!
//I don't think this will break anything. But really, do I ever?
return;
}
if (!cent->npcClient)
{
CG_CreateNPCClient(&cent->npcClient); //allocate memory for it
if (!cent->npcClient)
{
assert(0);
return;
}
memset(cent->npcClient, 0, sizeof(clientInfo_t));
cent->npcClient->ghoul2Model = NULL;
}
ci = cent->npcClient;
assert(ci);
//just force these guys to be set again, it won't hurt anything if they're
//already set.
cent->npcLocalSurfOff = 0;
cent->npcLocalSurfOn = 0;
}
else
{
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
}
while (i < MAX_SABERS)
{
j = 0;
while (j < ci->saber[i].numBlades)
{
ci->saber[i].blade[j].trail.lastTime = -20000;
j++;
}
i++;
}
ci->facial_blink = -1;
ci->facial_frown = 0;
ci->facial_aux = 0;
ci->superSmoothTime = 0;
//reset lerp origin smooth point
VectorCopy(cent->lerpOrigin, cent->beamEnd);
if (cent->currentState.eType != ET_NPC ||
!(cent->currentState.eFlags & EF_DEAD))
{
CG_ClearLerpFrame( cent, ci, &cent->pe.legs, cent->currentState.legsAnim, qfalse);
CG_ClearLerpFrame( cent, ci, &cent->pe.torso, cent->currentState.torsoAnim, qtrue);
BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
// VectorCopy( cent->lerpOrigin, cent->rawOrigin );
VectorCopy( cent->lerpAngles, cent->rawAngles );
memset( &cent->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( &cent->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 (cent->currentState.eType == ET_NPC)
{ //just start them off at 0 pitch
cent->pe.torso.pitchAngle = 0;
}
if ((cent->ghoul2 == NULL) && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
{
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cent->ghoul2);
cent->weapon = 0;
cent->ghoul2weapon = NULL;
//Attach the instance to this entity num so we can make use of client-server
//shared operations if possible.
trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse);
if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1)
{ //check now to see if we have this bone for setting anims and such
cent->noFace = qtrue;
}
cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2);
cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex);
//CG_CopyG2WeaponInstance(cent->currentState.weapon, ci->ghoul2Model);
//cent->weapon = cent->currentState.weapon;
}
}
//do this to prevent us from making a saber unholster sound the first time we enter the pvs
if (cent->currentState.number != cg.predictedPlayerState.clientNum &&
cent->currentState.weapon == WP_SABER &&
cent->weapon != cent->currentState.weapon)
{
cent->weapon = cent->currentState.weapon;
if (cent->ghoul2 && ci->ghoul2Model)
{
CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2);
cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon);
}
if (!cent->currentState.saberHolstered)
{ //if not holstered set length and desired length for both blades to full right now.
BG_SI_SetDesiredLength(&ci->saber[0], 0, -1);
BG_SI_SetDesiredLength(&ci->saber[1], 0, -1);
i = 0;
while (i < MAX_SABERS)
{
j = 0;
while (j < ci->saber[i].numBlades)
{
ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax;
j++;
}
i++;
}
}
}
if ( cg_debugPosition.integer ) {
CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
}
}