5966 lines
164 KiB
C
5966 lines
164 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"
|
|
|
|
|
|
extern stringID_table_t animTable [MAX_ANIMATIONS+1];
|
|
|
|
char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = {
|
|
"*death1.wav",
|
|
"*death2.wav",
|
|
"*death3.wav",
|
|
"*jump1.wav",
|
|
"*pain25.wav",
|
|
"*pain50.wav",
|
|
"*pain75.wav",
|
|
"*pain100.wav",
|
|
"*falling1.wav",
|
|
"*choke1.wav",
|
|
"*choke2.wav",
|
|
"*choke3.wav",
|
|
"*gasp.wav",
|
|
"*fall1.wav",
|
|
"*falling1.wav",
|
|
"*taunt.wav"
|
|
};
|
|
|
|
|
|
/*
|
|
================
|
|
CG_CustomSound
|
|
|
|
================
|
|
*/
|
|
sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) {
|
|
clientInfo_t *ci;
|
|
int i;
|
|
|
|
if ( soundName[0] != '*' ) {
|
|
return trap_S_RegisterSound( soundName );
|
|
}
|
|
|
|
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
|
|
clientNum = 0;
|
|
}
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) {
|
|
if ( !strcmp( soundName, cg_customSoundNames[i] ) ) {
|
|
return ci->sounds[i];
|
|
}
|
|
}
|
|
|
|
CG_Error( "Unknown custom sound: %s", soundName );
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
CLIENT INFO
|
|
|
|
=============================================================================
|
|
*/
|
|
/*
|
|
==========================
|
|
CG_FileExists
|
|
==========================
|
|
*/
|
|
static qboolean CG_FileExists(const char *filename) {
|
|
int len;
|
|
fileHandle_t f;
|
|
|
|
len = trap_FS_FOpenFile( filename, &f, FS_READ );
|
|
if (len>0) {
|
|
trap_FS_FCloseFile(f);
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
CG_FindClientModelFile
|
|
==========================
|
|
*/
|
|
static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) {
|
|
char *team, *charactersFolder;
|
|
int i;
|
|
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
switch ( ci->team ) {
|
|
case TEAM_BLUE: {
|
|
team = "blue";
|
|
break;
|
|
}
|
|
default: {
|
|
team = "red";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
team = "default";
|
|
}
|
|
charactersFolder = "";
|
|
while(1) {
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( i == 0 && teamName && *teamName ) {
|
|
// "models/players/characters/james/stroggs/lower_lily_red.skin"
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext );
|
|
}
|
|
else {
|
|
// "models/players/characters/james/lower_lily_red.skin"
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext );
|
|
}
|
|
if ( CG_FileExists( filename ) ) {
|
|
return qtrue;
|
|
}
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
if ( i == 0 && teamName && *teamName ) {
|
|
// "models/players/characters/james/stroggs/lower_red.skin"
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext );
|
|
}
|
|
else {
|
|
// "models/players/characters/james/lower_red.skin"
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext );
|
|
}
|
|
}
|
|
else {
|
|
if ( i == 0 && teamName && *teamName ) {
|
|
// "models/players/characters/james/stroggs/lower_lily.skin"
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext );
|
|
}
|
|
else {
|
|
// "models/players/characters/james/lower_lily.skin"
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext );
|
|
}
|
|
}
|
|
if ( CG_FileExists( filename ) ) {
|
|
return qtrue;
|
|
}
|
|
if ( !teamName || !*teamName ) {
|
|
break;
|
|
}
|
|
}
|
|
// if tried the heads folder first
|
|
if ( charactersFolder[0] ) {
|
|
break;
|
|
}
|
|
charactersFolder = "characters/";
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
CG_FindClientHeadFile
|
|
==========================
|
|
*/
|
|
static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) {
|
|
char *team, *headsFolder;
|
|
int i;
|
|
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
switch ( ci->team ) {
|
|
case TEAM_BLUE: {
|
|
team = "blue";
|
|
break;
|
|
}
|
|
default: {
|
|
team = "red";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
team = "default";
|
|
}
|
|
|
|
if ( headModelName[0] == '*' ) {
|
|
headsFolder = "heads/";
|
|
headModelName++;
|
|
}
|
|
else {
|
|
headsFolder = "";
|
|
}
|
|
while(1) {
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( i == 0 && teamName && *teamName ) {
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext );
|
|
}
|
|
else {
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext );
|
|
}
|
|
if ( CG_FileExists( filename ) ) {
|
|
return qtrue;
|
|
}
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
if ( i == 0 && teamName && *teamName ) {
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext );
|
|
}
|
|
else {
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext );
|
|
}
|
|
}
|
|
else {
|
|
if ( i == 0 && teamName && *teamName ) {
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext );
|
|
}
|
|
else {
|
|
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext );
|
|
}
|
|
}
|
|
if ( CG_FileExists( filename ) ) {
|
|
return qtrue;
|
|
}
|
|
if ( !teamName || !*teamName ) {
|
|
break;
|
|
}
|
|
}
|
|
// if tried the heads folder first
|
|
if ( headsFolder[0] ) {
|
|
break;
|
|
}
|
|
headsFolder = "heads/";
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
CG_RegisterClientSkin
|
|
==========================
|
|
*/
|
|
static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) {
|
|
char filename[MAX_QPATH];
|
|
|
|
/*
|
|
Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName );
|
|
ci->legsSkin = trap_R_RegisterSkin( filename );
|
|
if (!ci->legsSkin) {
|
|
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName );
|
|
ci->legsSkin = trap_R_RegisterSkin( filename );
|
|
if (!ci->legsSkin) {
|
|
Com_Printf( "Leg skin load failure: %s\n", filename );
|
|
}
|
|
}
|
|
|
|
|
|
Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName );
|
|
ci->torsoSkin = trap_R_RegisterSkin( filename );
|
|
if (!ci->torsoSkin) {
|
|
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName );
|
|
ci->torsoSkin = trap_R_RegisterSkin( filename );
|
|
if (!ci->torsoSkin) {
|
|
Com_Printf( "Torso skin load failure: %s\n", filename );
|
|
}
|
|
}
|
|
*/
|
|
if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) {
|
|
ci->legsSkin = trap_R_RegisterSkin( filename );
|
|
}
|
|
if (!ci->legsSkin) {
|
|
Com_Printf( "Leg skin load failure: %s\n", filename );
|
|
}
|
|
|
|
if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) {
|
|
ci->torsoSkin = trap_R_RegisterSkin( filename );
|
|
}
|
|
if (!ci->torsoSkin) {
|
|
Com_Printf( "Torso skin load failure: %s\n", filename );
|
|
}
|
|
|
|
if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) {
|
|
ci->headSkin = trap_R_RegisterSkin( filename );
|
|
}
|
|
if (!ci->headSkin) {
|
|
Com_Printf( "Head skin load failure: %s\n", filename );
|
|
}
|
|
|
|
// if any skins failed to load
|
|
if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) {
|
|
return qfalse;
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
CG_NeedAnimSequence
|
|
|
|
Called to check if models are missing required animation sequences
|
|
We do not list all used animations here but this check should eliminate
|
|
a broad range of unsupported models. At least the ones that are included,
|
|
which is all we really care about.
|
|
==========================
|
|
*/
|
|
qboolean CG_NeedAnimSequence(int anim)
|
|
{
|
|
if (anim >= BOTH_DEATH1 &&
|
|
anim <= BOTH_DEATH19)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if (anim >= BOTH_DISMEMBER_HEAD1 &&
|
|
anim <= BOTH_DISMEMBER_LARM)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if (anim >= BOTH_A1_T__B_ &&
|
|
anim <= BOTH_H1_S1_BR)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
CG_RegisterClientModelname
|
|
==========================
|
|
*/
|
|
static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, 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;
|
|
qboolean retriedAlready = qfalse;
|
|
retryModel:
|
|
if (ci->ATST && clientNum == -1)
|
|
{
|
|
Com_sprintf(ci->teamName, sizeof(ci->teamName), teamName);
|
|
return qtrue;
|
|
}
|
|
|
|
if (badModel)
|
|
{
|
|
modelName = "kyle";
|
|
headModelName = "kyle";
|
|
skinName = "default";
|
|
headSkinName = "default";
|
|
Com_Printf("WARNING: Attempted to load an unsupported multiplayer model! (bad or missing bone, or missing animation sequence)\n");
|
|
|
|
badModel = qfalse;
|
|
retriedAlready = qtrue;
|
|
}
|
|
|
|
// 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 (cgs.gametype >= GT_TEAM)
|
|
{
|
|
if (ci->team == TEAM_RED)
|
|
{
|
|
Com_sprintf(ci->skinName, sizeof(ci->skinName), "red");
|
|
skinName = "red";
|
|
}
|
|
else if (ci->team == TEAM_BLUE)
|
|
{
|
|
Com_sprintf(ci->skinName, sizeof(ci->skinName), "blue");
|
|
skinName = "blue";
|
|
}
|
|
}
|
|
|
|
if (clientNum != -1 && cg_entities[clientNum].currentState.teamowner && !cg_entities[clientNum].isATST)
|
|
{
|
|
handle = trap_G2API_InitGhoul2Model(&ci->ghoul2Model, "models/players/atst/model.glm", 0, 0, 0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", modelName );
|
|
handle = trap_G2API_InitGhoul2Model(&ci->ghoul2Model, afilename, 0, trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelName, skinName)), 0, 0, 0);
|
|
}
|
|
if (handle<0)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (clientNum != -1 && cg_entities[clientNum].currentState.teamowner && !cg_entities[clientNum].isATST)
|
|
{
|
|
ci->torsoSkin = 0;
|
|
ci->ATST = qtrue;
|
|
}
|
|
else
|
|
{
|
|
ci->torsoSkin = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelName, skinName));
|
|
ci->ATST = qfalse;
|
|
}
|
|
|
|
// The model is now loaded.
|
|
|
|
GLAName[0] = 0;
|
|
|
|
//get the location of the animation.cfg
|
|
//GLAName = trap_G2API_GetGLAName( ci->ghoul2Model, 0);
|
|
trap_G2API_GetGLAName( ci->ghoul2Model, 0, GLAName);
|
|
if (GLAName[0] == 0/*GLAName == NULL*/)
|
|
{
|
|
if (!BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", ci->animations))
|
|
{
|
|
Com_Printf( "Failed to load animation file %s\n", afilename );
|
|
return qfalse;
|
|
}
|
|
return qtrue;
|
|
}
|
|
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;
|
|
}
|
|
|
|
// Try to load the animation.cfg for this model then.
|
|
if ( !BG_ParseAnimationFile( afilename, ci->animations ) )
|
|
{ // The GLA's animations failed
|
|
if (!BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", ci->animations))
|
|
{
|
|
Com_Printf( "Failed to load animation file %s\n", afilename );
|
|
return qfalse;
|
|
}
|
|
}
|
|
else if (!retriedAlready)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < MAX_ANIMATIONS; i++)
|
|
{
|
|
if (!ci->animations[i].firstFrame && !ci->animations[i].numFrames && CG_NeedAnimSequence(i))
|
|
{ //using default for this animation so it obviously never got filled in.
|
|
//if it's a sequence that we need, this model must be an unsupported one.
|
|
badModel = qtrue;
|
|
goto retryModel;
|
|
}
|
|
}
|
|
}
|
|
// NOTE - ensure this sequence of bolt and bone accessing are always the same because the client expects them in a certain order
|
|
|
|
if (clientNum != -1 && cg_entities[clientNum].currentState.teamowner && !cg_entities[clientNum].isATST)
|
|
{
|
|
ci->torsoSkin = 0;
|
|
ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*flash1");
|
|
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, "l_clavical", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time);
|
|
trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "r_clavical", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time);
|
|
|
|
ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*flash2");
|
|
ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "pelvis");
|
|
}
|
|
else
|
|
{
|
|
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");
|
|
ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top");
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// ent->s.radius = 90;
|
|
|
|
if (clientNum != -1)
|
|
{
|
|
if (cg_entities[clientNum].isATST)
|
|
{
|
|
animation_t *anim;
|
|
|
|
anim = &ci->animations[ (cg_entities[clientNum].currentState.legsAnim & ~ANIM_TOGGLEBIT) ];
|
|
|
|
if (anim)
|
|
{
|
|
int flags = BONE_ANIM_OVERRIDE_FREEZE;
|
|
int firstFrame = anim->firstFrame + anim->numFrames-1;
|
|
|
|
if (anim->loopFrames != -1)
|
|
{
|
|
flags = BONE_ANIM_OVERRIDE_LOOP;
|
|
firstFrame = anim->firstFrame;
|
|
}
|
|
|
|
//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, 1.0f, cg.time, -1, 150);
|
|
|
|
cg_entities[clientNum].currentState.legsAnim = 0;
|
|
}
|
|
|
|
anim = &ci->animations[ (cg_entities[clientNum].currentState.torsoAnim & ~ANIM_TOGGLEBIT) ];
|
|
|
|
if (anim)
|
|
{
|
|
int flags = BONE_ANIM_OVERRIDE_FREEZE;
|
|
int firstFrame = anim->firstFrame + anim->numFrames-1;
|
|
|
|
if (anim->loopFrames != -1)
|
|
{
|
|
flags = BONE_ANIM_OVERRIDE_LOOP;
|
|
firstFrame = anim->firstFrame;
|
|
}
|
|
|
|
//rww - Set the animation again because it just got reset due to the model change
|
|
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "upper_lumbar", anim->firstFrame + anim->numFrames-1, anim->firstFrame + anim->numFrames, flags, 1.0f, cg.time, -1, 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);
|
|
|
|
cg_entities[clientNum].ghoul2weapon = NULL;
|
|
}
|
|
|
|
Com_sprintf(ci->teamName, sizeof(ci->teamName), teamName);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#define DEFAULT_FEMALE_SOUNDPATH "chars/mp_generic_female/misc"//"chars/tavion/misc"
|
|
#define DEFAULT_MALE_SOUNDPATH "chars/mp_generic_male/misc"//"chars/kyle/misc"
|
|
/*
|
|
===================
|
|
CG_LoadClientInfo
|
|
|
|
Load it now, taking the disk hits.
|
|
This will usually be deferred to a safe time
|
|
===================
|
|
*/
|
|
void CG_LoadClientInfo( clientInfo_t *ci ) {
|
|
const char *dir, *fallback;
|
|
int i, modelloaded;
|
|
const char *s;
|
|
int clientNum;
|
|
char teamname[MAX_QPATH];
|
|
int fLen = 0;
|
|
char soundpath[MAX_QPATH];
|
|
char soundName[1024];
|
|
qboolean isFemale = qfalse;
|
|
fileHandle_t f;
|
|
|
|
teamname[0] = 0;
|
|
if( cgs.gametype >= GT_TEAM) {
|
|
if( ci->team == TEAM_BLUE ) {
|
|
Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) );
|
|
} else {
|
|
Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) );
|
|
}
|
|
}
|
|
if( teamname[0] ) {
|
|
strcat( teamname, "/" );
|
|
}
|
|
modelloaded = qtrue;
|
|
if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname, -1 ) ) {
|
|
if ( cg_buildScript.integer ) {
|
|
CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname );
|
|
}
|
|
|
|
// 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_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname, -1 ) ) {
|
|
CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName );
|
|
}
|
|
} else {
|
|
if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname, -1 ) ) {
|
|
CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL );
|
|
}
|
|
}
|
|
modelloaded = qfalse;
|
|
}
|
|
|
|
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
|
|
dir = ci->modelName;
|
|
fallback = DEFAULT_MALE_SOUNDPATH; //(cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL;
|
|
|
|
fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ);
|
|
|
|
soundpath[0] = 0;
|
|
|
|
if (f)
|
|
{
|
|
i = 0;
|
|
|
|
trap_FS_Read(soundpath, fLen, f);
|
|
|
|
i = fLen;
|
|
|
|
while (i >= 0 && soundpath[i] != '\n')
|
|
{
|
|
if (soundpath[i] == 'f')
|
|
{
|
|
isFemale = qtrue;
|
|
}
|
|
|
|
i--;
|
|
}
|
|
|
|
soundpath[i-1] = '\0';
|
|
|
|
trap_FS_FCloseFile(f);
|
|
}
|
|
|
|
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);
|
|
//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])
|
|
{
|
|
if (CG_FileExists(va("sound/%s/%s.wav", soundpath, soundName)) ||
|
|
CG_FileExists(va("sound/%s/%s.mp3", soundpath, soundName)))
|
|
{
|
|
ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", soundpath, soundName) );
|
|
}
|
|
else
|
|
{
|
|
ci->sounds[i] = 0;
|
|
}
|
|
|
|
if (!ci->sounds[i])
|
|
{
|
|
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) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (modelloaded)
|
|
{
|
|
if (CG_FileExists(va("sound/chars/%s/misc/%s.wav", dir, soundName)) ||
|
|
CG_FileExists(va("sound/chars/%s/misc/%s.mp3", dir, soundName)))
|
|
{
|
|
ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) );
|
|
}
|
|
else
|
|
{
|
|
ci->sounds[i] = 0;
|
|
}
|
|
}
|
|
|
|
if ( !ci->sounds[i] )
|
|
{
|
|
ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", fallback, soundName) );
|
|
}
|
|
}
|
|
}
|
|
|
|
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] );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
======================
|
|
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;
|
|
|
|
// rww - was duplicating instance, now just reassigning pointer to same memory
|
|
to->ghoul2Model = from->ghoul2Model;
|
|
|
|
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->ATST = from->ATST;
|
|
|
|
memcpy( to->animations, from->animations, sizeof( to->animations ) );
|
|
memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_ScanForExistingClientInfo
|
|
======================
|
|
*/
|
|
static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) {
|
|
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->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)
|
|
&& 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;
|
|
|
|
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) ) {
|
|
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 ( Q_stricmp( ci->skinName, match->skinName ) ||
|
|
(cgs.gametype >= GT_TEAM && ci->team != match->team) ) {
|
|
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;
|
|
}
|
|
|
|
ci->deferred = qtrue;
|
|
CG_CopyClientInfoModel( match, ci );
|
|
return;
|
|
}
|
|
|
|
// we should never get here...
|
|
CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" );
|
|
|
|
CG_LoadClientInfo( ci );
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_NewClientInfo
|
|
======================
|
|
*/
|
|
void CG_NewClientInfo( int clientNum, qboolean entitiesInitialized ) {
|
|
clientInfo_t *ci;
|
|
clientInfo_t newInfo;
|
|
const char *configstring;
|
|
const char *v;
|
|
char *slash;
|
|
void *oldGhoul2;
|
|
int i = 0;
|
|
qboolean wasATST = qfalse;
|
|
|
|
ci = &cgs.clientinfo[clientNum];
|
|
|
|
oldGhoul2 = ci->ghoul2Model;
|
|
|
|
configstring = CG_ConfigString( clientNum + CS_PLAYERS );
|
|
if ( !configstring[0] ) {
|
|
memset( ci, 0, sizeof( *ci ) );
|
|
return; // player just left
|
|
}
|
|
|
|
if (ci)
|
|
{
|
|
wasATST = ci->ATST;
|
|
}
|
|
|
|
// 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 );
|
|
|
|
// 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 );
|
|
|
|
// 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;
|
|
|
|
if( cgs.gametype >= GT_TEAM ) {
|
|
Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) );
|
|
Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// head model
|
|
v = Info_ValueForKey( configstring, "hmodel" );
|
|
if ( cg_forceModel.integer ) {
|
|
// forcemodel makes everyone use a single model
|
|
// to prevent load hitches
|
|
char modelStr[MAX_QPATH];
|
|
char *skin;
|
|
|
|
if( cgs.gametype >= GT_TEAM ) {
|
|
Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) );
|
|
Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
|
|
} else {
|
|
trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) );
|
|
if ( ( skin = strchr( modelStr, '/' ) ) == NULL) {
|
|
skin = "default";
|
|
} else {
|
|
*skin++ = 0;
|
|
}
|
|
|
|
Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) );
|
|
Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) );
|
|
}
|
|
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
// keep skin name
|
|
slash = strchr( v, '/' );
|
|
if ( slash ) {
|
|
Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) );
|
|
}
|
|
}
|
|
} else {
|
|
Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) );
|
|
|
|
slash = strchr( newInfo.headModelName, '/' );
|
|
if ( !slash ) {
|
|
// modelName didn not include a skin name
|
|
Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
|
|
} else {
|
|
Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) );
|
|
// truncate modelName
|
|
*slash = 0;
|
|
}
|
|
}
|
|
|
|
// force powers
|
|
v = Info_ValueForKey( configstring, "forcepowers" );
|
|
Q_strncpyz( newInfo.forcePowers, v, sizeof( newInfo.forcePowers ) );
|
|
|
|
newInfo.ATST = wasATST;
|
|
|
|
// scan for an existing clientinfo that matches this modelname
|
|
// so we can avoid loading checks if possible
|
|
if ( !CG_ScanForExistingClientInfo( &newInfo ) ) {
|
|
qboolean forceDefer;
|
|
|
|
forceDefer = trap_MemoryRemaining() < 4000000;
|
|
|
|
// if we are defering loads, just have it pick the first valid
|
|
if ( forceDefer || ( cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) {
|
|
// keep whatever they had if it won't violate team skins
|
|
CG_SetDeferredClientInfo( &newInfo );
|
|
// if we are low on memory, leave them with this model
|
|
if ( forceDefer ) {
|
|
CG_Printf( "Memory is low. Using deferred model.\n" );
|
|
newInfo.deferred = qfalse;
|
|
}
|
|
} else {
|
|
CG_LoadClientInfo( &newInfo );
|
|
}
|
|
}
|
|
|
|
// replace whatever was there with the new one
|
|
newInfo.infoValid = qtrue;
|
|
*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++;
|
|
}
|
|
|
|
// 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;
|
|
// First check if we have a ghoul2 model on the client entity.
|
|
if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2))
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2);
|
|
}
|
|
|
|
anim = &ci->animations[ (cg_entities[clientNum].currentState.legsAnim & ~ANIM_TOGGLEBIT) ];
|
|
|
|
if (anim)
|
|
{
|
|
int flags = BONE_ANIM_OVERRIDE_FREEZE;
|
|
int firstFrame = anim->firstFrame + anim->numFrames-1;
|
|
|
|
if (anim->loopFrames != -1)
|
|
{
|
|
flags = BONE_ANIM_OVERRIDE_LOOP;
|
|
firstFrame = anim->firstFrame;
|
|
}
|
|
|
|
//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, 1.0f, cg.time, -1, 150);
|
|
|
|
cg_entities[clientNum].currentState.legsAnim = 0;
|
|
}
|
|
|
|
anim = &ci->animations[ (cg_entities[clientNum].currentState.torsoAnim & ~ANIM_TOGGLEBIT) ];
|
|
|
|
if (anim)
|
|
{
|
|
int flags = BONE_ANIM_OVERRIDE_FREEZE;
|
|
int firstFrame = anim->firstFrame + anim->numFrames-1;
|
|
|
|
if (anim->loopFrames != -1)
|
|
{
|
|
flags = BONE_ANIM_OVERRIDE_LOOP;
|
|
firstFrame = anim->firstFrame;
|
|
}
|
|
|
|
//rww - Set the animation again because it just got reset due to the model change
|
|
trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "upper_lumbar", anim->firstFrame + anim->numFrames-1, anim->firstFrame + anim->numFrames, flags, 1.0f, cg.time, -1, 150);
|
|
|
|
cg_entities[clientNum].currentState.torsoAnim = 0;
|
|
}
|
|
|
|
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2);
|
|
//rww - FIXME: Perhaps I'm missing something, but if we're duplicating the instance from ci instead of pointing the cent g2 instance to it, can't we clean one up? (no reason to have
|
|
// the same instance duplicated in memory) I'm not touching this at the moment because I don't want to unknowingly break something horribly if I'm wrong
|
|
|
|
if (cg_entities[clientNum].weapon > WP_NONE)
|
|
{
|
|
CG_CopyG2WeaponInstance(cg_entities[clientNum].weapon, cg_entities[clientNum].ghoul2);
|
|
}
|
|
}
|
|
else if (ci->team == TEAM_SPECTATOR && cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2))
|
|
{ //rww - not doing this seems to allow .ghoul2 to become an invalid pointer. And as we all know, invalid pointers make baby Carmack cry.
|
|
trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2);
|
|
trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
======================
|
|
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 ) {
|
|
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 ) {
|
|
// if we are low on memory, leave it deferred
|
|
if ( trap_MemoryRemaining() < 4000000 ) {
|
|
CG_Printf( "Memory is low. Using deferred model.\n" );
|
|
ci->deferred = qfalse;
|
|
continue;
|
|
}
|
|
CG_LoadClientInfo( ci );
|
|
// break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PLAYER ANIMATION
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
static qboolean CG_FirstAnimFrame(clientInfo_t *ci, lerpFrame_t *lf, qboolean torsoOnly, float speedScale);
|
|
|
|
qboolean CG_DeathAnim( int anim )
|
|
{
|
|
switch((anim&~ANIM_TOGGLEBIT))
|
|
{
|
|
case BOTH_DIVE1:
|
|
case BOTH_DEATHBACKWARD1:
|
|
case BOTH_DEATHBACKWARD2:
|
|
case BOTH_DEATHFORWARD1:
|
|
case BOTH_DEATHFORWARD2:
|
|
case BOTH_DEATH1:
|
|
case BOTH_DEATH2:
|
|
case BOTH_DEATH3:
|
|
case BOTH_DEATH4:
|
|
case BOTH_DEATH5:
|
|
case BOTH_DEATH6:
|
|
case BOTH_DEATH7:
|
|
|
|
case BOTH_DEATH1IDLE:
|
|
case BOTH_LYINGDEATH1:
|
|
case BOTH_STUMBLEDEATH1:
|
|
case BOTH_FALLDEATH1:
|
|
case BOTH_FALLDEATH1INAIR:
|
|
case BOTH_FALLDEATH1LAND:
|
|
return qtrue;
|
|
break;
|
|
default:
|
|
return qfalse;
|
|
break;
|
|
}
|
|
}
|
|
|
|
qboolean CG_InRoll( centity_t *cent )
|
|
{
|
|
switch ( (cent->currentState.legsAnim&~ANIM_TOGGLEBIT) )
|
|
{
|
|
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 PM_SaberInSpecial( int move );
|
|
qboolean PM_SaberInSpecialAttack( int anim );
|
|
|
|
/*
|
|
===============
|
|
CG_SetLerpFrameAnimation
|
|
|
|
may include ANIM_TOGGLEBIT
|
|
===============
|
|
*/
|
|
static void CG_SetLerpFrameAnimation( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float animSpeedMult, qboolean torsoOnly) {
|
|
animation_t *anim;
|
|
float animSpeed;
|
|
int flags=BONE_ANIM_OVERRIDE_FREEZE;
|
|
int oldAnim = -1;
|
|
|
|
if (cent->currentState.number < MAX_CLIENTS &&
|
|
cent->currentState.teamowner &&
|
|
!cent->isATST)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cent->isATST)
|
|
{
|
|
vec3_t testVel;
|
|
float fVel;
|
|
|
|
if (lf->animationNumber == BOTH_RUN1 ||
|
|
lf->animationNumber == BOTH_WALK1 ||
|
|
lf->animationNumber == BOTH_WALKBACK1 ||
|
|
lf->animationNumber == BOTH_TURN_LEFT1 ||
|
|
lf->animationNumber == BOTH_TURN_RIGHT1)
|
|
{
|
|
if (cent->atstFootClang < cg.time)
|
|
{
|
|
if (rand() & 1)
|
|
{
|
|
trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/chars/atst/ATSTstep1.wav"));
|
|
}
|
|
else
|
|
{
|
|
trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/chars/atst/ATSTstep2.wav"));
|
|
}
|
|
|
|
cent->atstFootClang = cg.time + 1300;
|
|
}
|
|
}
|
|
|
|
VectorCopy(cent->currentState.pos.trDelta, testVel);
|
|
|
|
fVel = VectorNormalize(testVel);
|
|
|
|
if (cent->currentState.eFlags & EF_DEAD)
|
|
{
|
|
if (lf->animationNumber != BOTH_DEATH1 &&
|
|
lf->animationNumber != BOTH_DEAD1)
|
|
{
|
|
trap_S_StartSound(NULL, cent->currentState.number, CHAN_BODY, trap_S_RegisterSound("sound/chars/atst/ATSTcrash.wav"));
|
|
}
|
|
newAnimation = BOTH_DEATH1;
|
|
}
|
|
else if (fVel > 0)
|
|
{
|
|
qboolean doNotSet = qfalse;
|
|
|
|
if (fVel > 250)
|
|
{
|
|
animSpeedMult = 1.2;
|
|
}
|
|
else if (fVel > 200)
|
|
{
|
|
animSpeedMult = 1;
|
|
}
|
|
else if (fVel > 100)
|
|
{
|
|
animSpeedMult = 0.9;
|
|
}
|
|
else if (fVel > 0)
|
|
{
|
|
animSpeedMult = 0.8;
|
|
}
|
|
else
|
|
{
|
|
doNotSet = qtrue;
|
|
}
|
|
|
|
if (!doNotSet)
|
|
{
|
|
if ((cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) == BOTH_RUN1 ||
|
|
(cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) == BOTH_WALKBACK1 ||
|
|
(cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) == BOTH_WALK1)
|
|
{
|
|
newAnimation = (cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT);
|
|
}
|
|
else
|
|
{
|
|
newAnimation = BOTH_RUN1;
|
|
}
|
|
}
|
|
}
|
|
else if ((cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) == BOTH_RUN1START)
|
|
{
|
|
if (lf->animationNumber != BOTH_RUN1START)
|
|
{
|
|
trap_S_StartSound(NULL, cent->currentState.number, CHAN_BODY, trap_S_RegisterSound("sound/chars/atst/ATSTstart.wav"));
|
|
cent->atstFootClang = cg.time + 650;
|
|
}
|
|
newAnimation = BOTH_RUN1START;
|
|
}
|
|
else if (cent->pe.legs.yawing &&
|
|
(cent->pe.legs.yawSwingDif < -20 || cent->pe.legs.yawSwingDif > 20))
|
|
{
|
|
if (cent->pe.legs.yawSwingDif > 0)
|
|
{
|
|
newAnimation = BOTH_TURN_LEFT1;//BOTH_RUN1;
|
|
if (lf->animationNumber != BOTH_TURN_LEFT1)
|
|
{
|
|
cent->atstFootClang = cg.time + 500;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newAnimation = BOTH_TURN_RIGHT1;//BOTH_RUN1;
|
|
if (lf->animationNumber != BOTH_TURN_RIGHT1)
|
|
{
|
|
cent->atstFootClang = cg.time + 500;
|
|
}
|
|
}
|
|
animSpeedMult = 0.7;
|
|
}
|
|
else
|
|
{
|
|
newAnimation = BOTH_STAND1;
|
|
}
|
|
|
|
if (newAnimation != BOTH_STAND1 && newAnimation != BOTH_RUN1 &&
|
|
newAnimation != BOTH_RUN1START && newAnimation != BOTH_WALK1 &&
|
|
newAnimation != BOTH_WALKBACK1 && newAnimation != BOTH_DEATH1 &&
|
|
newAnimation != BOTH_DEAD1 && newAnimation != BOTH_TURN_RIGHT1 &&
|
|
newAnimation != BOTH_TURN_LEFT1 && newAnimation != BOTH_PAIN1 &&
|
|
newAnimation != BOTH_PAIN2)
|
|
{ //not a valid anim for the ATST..
|
|
newAnimation = BOTH_RUN1;
|
|
}
|
|
|
|
if (lf->animationNumber == newAnimation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if (lf->animationNumber != BOTH_STAND1 &&
|
|
// ((lf->frame-lf->animation->firstFrame) < (lf->animation->numFrames/2)-8 ||
|
|
// (lf->frame-lf->animation->firstFrame) > (lf->animation->numFrames/2)+8))
|
|
// {
|
|
// return;
|
|
// }
|
|
// if (lf->animationNumber != BOTH_STAND1 &&
|
|
// !CG_FirstAnimFrame(ci, lf, torsoOnly, animSpeedMult))
|
|
// {
|
|
// return;
|
|
// }
|
|
}
|
|
|
|
oldAnim = lf->animationNumber;
|
|
|
|
lf->animationNumber = newAnimation;
|
|
newAnimation &= ~ANIM_TOGGLEBIT;
|
|
|
|
if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) {
|
|
CG_Error( "Bad animation number: %i", newAnimation );
|
|
}
|
|
|
|
anim = &ci->animations[ newAnimation ];
|
|
|
|
lf->animation = anim;
|
|
lf->animationTime = lf->frameTime + anim->initialLerp;
|
|
|
|
if ( cg_debugAnim.integer ) {
|
|
CG_Printf( "Anim: %i, '%s'\n", newAnimation, GetStringForID(animTable, newAnimation));
|
|
}
|
|
|
|
if (cent->ghoul2)
|
|
{
|
|
animSpeed = 50.0f / anim->frameLerp;
|
|
if (lf->animation->loopFrames != -1)
|
|
{
|
|
flags = BONE_ANIM_OVERRIDE_LOOP;
|
|
}
|
|
|
|
if (cent->isATST)
|
|
{
|
|
if (animSpeed < 0.3)
|
|
{
|
|
animSpeed = 0.3;
|
|
}
|
|
|
|
if (newAnimation == BOTH_WALKBACK1)
|
|
{
|
|
animSpeed = 0.8;
|
|
}
|
|
|
|
if (newAnimation != BOTH_DEATH1)
|
|
{
|
|
flags = BONE_ANIM_OVERRIDE_LOOP;
|
|
}
|
|
}
|
|
|
|
if (cg_animBlend.integer)
|
|
{
|
|
flags |= BONE_ANIM_BLEND;
|
|
}
|
|
|
|
if (!cent->isATST)
|
|
{
|
|
if (BG_FlippingAnim(newAnimation) || CG_DeathAnim(newAnimation))
|
|
{
|
|
flags &= ~BONE_ANIM_BLEND;
|
|
}
|
|
else if ( oldAnim != -1 &&
|
|
(BG_FlippingAnim(oldAnim) || CG_DeathAnim(oldAnim)) )
|
|
{
|
|
flags &= ~BONE_ANIM_BLEND;
|
|
}
|
|
}
|
|
|
|
animSpeed *= animSpeedMult;
|
|
|
|
if (torsoOnly)
|
|
{
|
|
lf->animationTorsoSpeed = animSpeedMult;
|
|
}
|
|
else
|
|
{
|
|
lf->animationSpeed = animSpeedMult;
|
|
}
|
|
|
|
if (cent->isATST)
|
|
{
|
|
int atstBlend = 400;
|
|
|
|
if (torsoOnly)
|
|
{
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "pelvis", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, lf->frameTime, -1, atstBlend);
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, lf->frameTime, -1, atstBlend);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (torsoOnly)
|
|
{
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "upper_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, lf->frameTime, -1, 150);
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, lf->frameTime, -1, 150);
|
|
}
|
|
|
|
if ((cent->currentState.torsoAnim&~ANIM_TOGGLEBIT) == newAnimation &&
|
|
!BG_FlippingAnim( cent->currentState.legsAnim ) &&
|
|
!BG_SpinningSaberAnim( cent->currentState.legsAnim ) &&
|
|
!BG_SpinningSaberAnim( cent->currentState.torsoAnim ) &&
|
|
!BG_InSpecialJump( cent->currentState.legsAnim ) &&
|
|
!BG_InSpecialJump( cent->currentState.torsoAnim ) &&
|
|
!CG_DeathAnim(cent->currentState.legsAnim) &&
|
|
!CG_DeathAnim(cent->currentState.torsoAnim) &&
|
|
!CG_InRoll(cent) &&
|
|
!PM_SaberInSpecial(cent->currentState.saberMove) &&
|
|
!PM_SaberInSpecialAttack(cent->currentState.torsoAnim) &&
|
|
!PM_SaberInSpecialAttack(cent->currentState.legsAnim) )
|
|
{
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, lf->frameTime, -1, 150);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
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(clientInfo_t *ci, lerpFrame_t *lf, qboolean torsoOnly, float speedScale)
|
|
{
|
|
if (torsoOnly)
|
|
{
|
|
if (lf->animationTorsoSpeed == speedScale)
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (lf->animationSpeed == speedScale)
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if (lf->animation->numFrames < 2)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if (lf->animation->firstFrame == lf->frame)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_InWalkingAnim
|
|
|
|
Returns last frame to step on if the animation on the frame is desired for playing footstep sounds,
|
|
otherwise 0
|
|
===============
|
|
*/
|
|
int CG_InWalkingAnim(int animNum)
|
|
{
|
|
int anim = animNum;
|
|
|
|
anim &= ~ANIM_TOGGLEBIT;
|
|
|
|
if (anim == BOTH_WALL_RUN_RIGHT ||
|
|
anim == BOTH_WALL_RUN_LEFT)
|
|
{
|
|
return 9;
|
|
}
|
|
|
|
if (anim >= BOTH_WALK1 &&
|
|
anim <= BOTH_RUNAWAY1)
|
|
{
|
|
if (anim == BOTH_RUN1)
|
|
{
|
|
return 12;
|
|
}
|
|
else
|
|
{
|
|
return 9;
|
|
}
|
|
}
|
|
|
|
if (anim >= BOTH_WALKBACK1 &&
|
|
anim <= BOTH_RUNBACK2)
|
|
{
|
|
return 9;
|
|
}
|
|
|
|
if (anim >= LEGS_WALKBACK1 &&
|
|
anim <= LEGS_RUNBACK2)
|
|
{
|
|
return 9;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
#define FOOTSTEP_GENERIC 1
|
|
#define FOOTSTEP_METAL 2
|
|
|
|
static int CG_FootstepForSurface( centity_t *cent, int skip )
|
|
{
|
|
trace_t tr;
|
|
vec3_t org, dOrg, legDir, bAngles;
|
|
vec3_t playerMins = {-15, -15, DEFAULT_MINS_2};
|
|
vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2};
|
|
mdxaBone_t boltMatrix;
|
|
|
|
// VectorCopy(ent->lerpOrigin, org);
|
|
// VectorCopy(org, dOrg);
|
|
// dOrg[2] -= 64; //should never have to go further than this because we're already onground
|
|
|
|
//We are now tracing off of a bolt point for footsteps, so that if we happen to be walking sideways on a wall we will
|
|
//still play step sounds properly.
|
|
|
|
VectorCopy(cent->lerpAngles, bAngles);
|
|
bAngles[PITCH] = 0;
|
|
|
|
trap_G2API_GetBoltMatrix_NoReconstruct( cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].bolt_llumbar, &boltMatrix, bAngles, cent->lerpOrigin, cg.time, /*cgs.gameModels*/0, cent->modelScale);
|
|
trap_G2API_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org );
|
|
trap_G2API_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, legDir );
|
|
|
|
dOrg[0] = org[0] + legDir[0]*64;
|
|
dOrg[1] = org[1] + legDir[1]*64;
|
|
dOrg[2] = org[2] + legDir[2]*64;
|
|
|
|
//CG_TestLine(org, dOrg, 100, 0x0000ff, 3);
|
|
|
|
CG_Trace(&tr, org, playerMins, playerMaxs, dOrg, skip, MASK_PLAYERSOLID);
|
|
|
|
if (tr.fraction == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( tr.surfaceFlags & SURF_NOSTEPS )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( tr.surfaceFlags & SURF_METALSTEPS )
|
|
{
|
|
return FOOTSTEP_METAL;
|
|
}
|
|
|
|
return FOOTSTEP_GENERIC;
|
|
}
|
|
|
|
void CG_FootStep(centity_t *cent, clientInfo_t *ci, int anim)
|
|
{
|
|
int groundType;
|
|
|
|
if ((anim & ~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT ||
|
|
(anim & ~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_LEFT)
|
|
{
|
|
groundType = FOOTSTEP_GENERIC;
|
|
goto skipCheck;
|
|
}
|
|
|
|
if (cent->currentState.groundEntityNum == ENTITYNUM_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
skipCheck:
|
|
groundType = CG_FootstepForSurface(cent, cent->currentState.number);
|
|
|
|
//skipCheck:
|
|
|
|
if (!groundType)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (groundType)
|
|
{
|
|
case FOOTSTEP_GENERIC:
|
|
trap_S_StartSound (NULL, cent->currentState.number, CHAN_BODY,
|
|
cgs.media.footsteps[ ci->footsteps ][rand()&3] );
|
|
break;
|
|
case FOOTSTEP_METAL:
|
|
trap_S_StartSound (NULL, cent->currentState.number, CHAN_BODY,
|
|
cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
===============
|
|
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, int newAnimation, float speedScale, qboolean torsoOnly)
|
|
{
|
|
int f, numFrames;
|
|
animation_t *anim;
|
|
|
|
// debugging tool to get no animations
|
|
if ( cg_animSpeed.integer == 0 ) {
|
|
lf->oldFrame = lf->frame = lf->backlerp = 0;
|
|
return;
|
|
}
|
|
|
|
// see if the animation sequence is switching
|
|
if (cent->currentState.forceFrame)
|
|
{
|
|
int flags = BONE_ANIM_OVERRIDE_FREEZE; //|BONE_ANIM_BLEND;
|
|
float animSpeed = 1.0f;
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "upper_lumbar", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, lf->frameTime, -1, 150);
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, lf->frameTime, -1, 150);
|
|
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, lf->frameTime, -1, 150);
|
|
|
|
lf->animationNumber = 0;
|
|
}
|
|
else if ( (newAnimation != lf->animationNumber || !lf->animation) || (CG_FirstAnimFrame(ci, lf, torsoOnly, speedScale)) )
|
|
{
|
|
CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation, speedScale, torsoOnly);
|
|
}
|
|
else if (cent->isATST)
|
|
{
|
|
if (cent->pe.legs.yawing != !lf->torsoYawing)
|
|
{
|
|
CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation, speedScale, torsoOnly);
|
|
lf->torsoYawing = cent->pe.legs.yawing;
|
|
}
|
|
}
|
|
|
|
// if we have passed the current frame, move it to
|
|
// oldFrame and calculate a new frame
|
|
if ( cg.time >= lf->frameTime ) {
|
|
if (lf->oldFrame != lf->frame &&
|
|
lf == &(cent->pe.legs))
|
|
{
|
|
int addFinalFrame = CG_InWalkingAnim(lf->animationNumber); //9;
|
|
|
|
if (addFinalFrame && !cent->isATST)
|
|
{
|
|
if ( lf->frame >= (lf->animation->firstFrame+3) &&
|
|
lf->oldFrame < (lf->animation->firstFrame+3))
|
|
{
|
|
CG_FootStep(cent, ci, lf->animationNumber);
|
|
}
|
|
else if ( lf->frame >= (lf->animation->firstFrame+addFinalFrame) &&
|
|
lf->oldFrame < (lf->animation->firstFrame+addFinalFrame))
|
|
{
|
|
CG_FootStep(cent, ci, lf->animationNumber);
|
|
}
|
|
else if (lf->oldFrame > lf->frame && lf->frame > (lf->animation->firstFrame+1))
|
|
{ //missed one
|
|
CG_FootStep(cent, ci, lf->animationNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
lf->oldFrame = lf->frame;
|
|
lf->oldFrameTime = lf->frameTime;
|
|
|
|
// get the next frame based on the animation
|
|
anim = lf->animation;
|
|
if ( !anim->frameLerp ) {
|
|
return; // shouldn't happen
|
|
}
|
|
|
|
if ( cg.time < lf->animationTime ) {
|
|
lf->frameTime = lf->animationTime; // initial lerp
|
|
} else {
|
|
lf->frameTime = lf->oldFrameTime + anim->frameLerp;
|
|
}
|
|
f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
|
|
f *= speedScale; // adjust for haste, etc
|
|
|
|
numFrames = anim->numFrames;
|
|
if (anim->flipflop) {
|
|
numFrames *= 2;
|
|
}
|
|
if ( f >= numFrames ) {
|
|
f -= numFrames;
|
|
if ( anim->loopFrames != -1 ) //Before 0 meant no loop
|
|
{
|
|
if(anim->numFrames - anim->loopFrames == 0)
|
|
{
|
|
f %= anim->numFrames;
|
|
}
|
|
else
|
|
{
|
|
f %= (anim->numFrames - anim->loopFrames);
|
|
}
|
|
f += anim->loopFrames;
|
|
}
|
|
else
|
|
{
|
|
f = numFrames - 1;
|
|
// the animation is stuck at the end, so it
|
|
// can immediately transition to another sequence
|
|
lf->frameTime = cg.time;
|
|
}
|
|
}
|
|
if ( anim->reversed ) {
|
|
lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
|
|
}
|
|
else if (anim->flipflop && f>=anim->numFrames) {
|
|
lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames);
|
|
}
|
|
else {
|
|
lf->frame = anim->firstFrame + f;
|
|
}
|
|
if ( cg.time > lf->frameTime ) {
|
|
lf->frameTime = cg.time;
|
|
if ( cg_debugAnim.integer ) {
|
|
CG_Printf( "Clamp lf->frameTime\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
lf->oldFrame = lf->frame = lf->animation->firstFrame;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerAnimation
|
|
===============
|
|
*/
|
|
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 (cent->currentState.forcePowersActive & (1 << FP_RAGE))
|
|
{
|
|
speedScale = 1.3;
|
|
}
|
|
else if (cent->currentState.forcePowersActive & (1 << FP_SPEED))
|
|
{
|
|
speedScale = 1.7;
|
|
}
|
|
else
|
|
{
|
|
speedScale = 1;
|
|
}
|
|
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
// do the shuffle turn frames locally
|
|
if(0){// if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == TORSO_WEAPONREADY3) {
|
|
CG_RunLerpFrame( cent, ci, ¢->pe.legs, LEGS_TURN1, speedScale, qfalse);
|
|
} else {
|
|
CG_RunLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale, qfalse);
|
|
}
|
|
|
|
if (!(cent->currentState.forcePowersActive & (1 << FP_RAGE)))
|
|
{ //don't affect torso anim speed unless raged
|
|
speedScale = 1;
|
|
}
|
|
else
|
|
{
|
|
speedScale = 1.7;
|
|
}
|
|
|
|
*legsOld = cent->pe.legs.oldFrame;
|
|
*legs = cent->pe.legs.frame;
|
|
*legsBackLerp = cent->pe.legs.backlerp;
|
|
|
|
CG_RunLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale, qtrue );
|
|
|
|
*torsoOld = cent->pe.torso.oldFrame;
|
|
*torso = cent->pe.torso.frame;
|
|
*torsoBackLerp = cent->pe.torso.backlerp;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PLAYER ANGLES
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
/*
|
|
==================
|
|
CG_SwingAngles
|
|
==================
|
|
*/
|
|
static float CG_SwingAngles( float destination, float swingTolerance, float clampTolerance,
|
|
float speed, float *angle, qboolean *swinging ) {
|
|
float swing;
|
|
float move;
|
|
float scale;
|
|
|
|
if ( !*swinging ) {
|
|
// see if a swing should be started
|
|
swing = AngleSubtract( *angle, destination );
|
|
if ( swing > swingTolerance || swing < -swingTolerance ) {
|
|
*swinging = qtrue;
|
|
}
|
|
}
|
|
|
|
if ( !*swinging ) {
|
|
return 0;
|
|
}
|
|
|
|
// modify the speed depending on the delta
|
|
// so it doesn't seem so linear
|
|
swing = AngleSubtract( destination, *angle );
|
|
scale = fabs( swing );
|
|
if ( scale < swingTolerance * 0.5 ) {
|
|
scale = 0.5;
|
|
} else if ( scale < swingTolerance ) {
|
|
scale = 1.0;
|
|
} else {
|
|
scale = 2.0;
|
|
}
|
|
|
|
// swing towards the destination angle
|
|
if ( swing >= 0 ) {
|
|
move = cg.frametime * scale * speed;
|
|
if ( move >= swing ) {
|
|
move = swing;
|
|
*swinging = qfalse;
|
|
}
|
|
*angle = AngleMod( *angle + move );
|
|
} else if ( swing < 0 ) {
|
|
move = cg.frametime * scale * -speed;
|
|
if ( move <= swing ) {
|
|
move = swing;
|
|
*swinging = qfalse;
|
|
}
|
|
*angle = AngleMod( *angle + move );
|
|
}
|
|
|
|
// clamp to no more than tolerance
|
|
swing = AngleSubtract( destination, *angle );
|
|
if ( swing > clampTolerance ) {
|
|
*angle = AngleMod( destination - (clampTolerance - 1) );
|
|
} else if ( swing < -clampTolerance ) {
|
|
*angle = AngleMod( destination + (clampTolerance - 1) );
|
|
}
|
|
|
|
return swing;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_SwingAngles
|
|
==================
|
|
*/
|
|
static float CG_SwingAnglesATST( centity_t *cent, float destination, float swingTolerance, float clampTolerance,
|
|
float speed, float *angle, qboolean *swinging ) {
|
|
float swing;
|
|
float move;
|
|
float scale;
|
|
|
|
if ( !*swinging ) {
|
|
// see if a swing should be started
|
|
swing = AngleSubtract( *angle, destination );
|
|
if ( swing > swingTolerance || swing < -swingTolerance ) {
|
|
*swinging = qtrue;
|
|
}
|
|
}
|
|
|
|
if ( !*swinging ) {
|
|
return 0;
|
|
}
|
|
|
|
// modify the speed depending on the delta
|
|
// so it doesn't seem so linear
|
|
swing = AngleSubtract( destination, *angle );
|
|
scale = fabs( swing );
|
|
if ( scale < swingTolerance * 0.5 ) {
|
|
scale = 0.5;
|
|
} else if ( scale < swingTolerance ) {
|
|
scale = 1.0;
|
|
} else {
|
|
scale = 2.0;
|
|
}
|
|
|
|
if (swing < 10 && swing > -10)
|
|
{
|
|
cent->atstSwinging = 0;
|
|
}
|
|
if (swing < 60 && swing > -60 && !cent->atstSwinging)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
cent->atstSwinging = 1;
|
|
|
|
// swing towards the destination angle
|
|
if ( swing >= 0 ) {
|
|
move = cg.frametime * scale * speed;
|
|
if ( move >= swing ) {
|
|
move = swing;
|
|
*swinging = qfalse;
|
|
}
|
|
*angle = AngleMod( *angle + move );
|
|
} else if ( swing < 0 ) {
|
|
move = cg.frametime * scale * -speed;
|
|
if ( move <= swing ) {
|
|
move = swing;
|
|
*swinging = qfalse;
|
|
}
|
|
*angle = AngleMod( *angle + move );
|
|
}
|
|
|
|
// clamp to no more than tolerance
|
|
swing = AngleSubtract( destination, *angle );
|
|
if ( swing > clampTolerance ) {
|
|
*angle = AngleMod( destination - (clampTolerance - 1) );
|
|
} else if ( swing < -clampTolerance ) {
|
|
*angle = AngleMod( destination + (clampTolerance - 1) );
|
|
}
|
|
|
|
return swing;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_AddPainTwitch
|
|
=================
|
|
*/
|
|
static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) {
|
|
int t;
|
|
float f;
|
|
|
|
t = cg.time - cent->pe.painTime;
|
|
if ( t >= PAIN_TWITCH_TIME ) {
|
|
return;
|
|
}
|
|
|
|
f = 1.0 - (float)t / PAIN_TWITCH_TIME;
|
|
|
|
if ( cent->pe.painDirection ) {
|
|
torsoAngles[ROLL] += 20 * f;
|
|
} else {
|
|
torsoAngles[ROLL] -= 20 * f;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerAngles
|
|
|
|
Handles seperate torso motion
|
|
|
|
legs pivot based on direction of movement
|
|
|
|
head always looks exactly at cent->lerpAngles
|
|
|
|
if motion < 20 degrees, show in head only
|
|
if < 45 degrees, also show in torso
|
|
===============
|
|
*/
|
|
|
|
static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
|
|
vec3_t legsAngles, torsoAngles, headAngles;
|
|
float dest;
|
|
static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
|
|
vec3_t velocity;
|
|
float speed;
|
|
int dir, clientNum;
|
|
clientInfo_t *ci;
|
|
|
|
VectorCopy( cent->lerpAngles, headAngles );
|
|
headAngles[YAW] = AngleMod( headAngles[YAW] );
|
|
VectorClear( legsAngles );
|
|
VectorClear( torsoAngles );
|
|
|
|
// --------- yaw -------------
|
|
|
|
// allow yaw to drift a bit
|
|
if ((( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_STAND1) ||
|
|
( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != WeaponReadyAnim[cent->currentState.weapon] ) {
|
|
// if not standing still, always point all in the same direction
|
|
cent->pe.torso.yawing = qtrue; // always center
|
|
cent->pe.torso.pitching = qtrue; // always center
|
|
cent->pe.legs.yawing = qtrue; // always center
|
|
}
|
|
|
|
// adjust legs for movement dir
|
|
if ( cent->currentState.eFlags & EF_DEAD ) {
|
|
// don't let dead bodies twitch
|
|
dir = 0;
|
|
} else {
|
|
dir = cent->currentState.angles2[YAW];
|
|
if ( dir < 0 || dir > 7 ) {
|
|
CG_Error( "Bad player movement angle" );
|
|
}
|
|
}
|
|
legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ];
|
|
torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ];
|
|
|
|
// torso
|
|
CG_SwingAngles( torsoAngles[YAW], 25, 90, /*cg_swingSpeed.value*/ 0.3, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing );
|
|
CG_SwingAngles( legsAngles[YAW], 40, 90, /*cg_swingSpeed.value*/ 0.3, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
|
|
|
|
torsoAngles[YAW] = cent->pe.torso.yawAngle;
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
|
|
// --------- pitch -------------
|
|
|
|
// only show a fraction of the pitch angle in the torso
|
|
if ( headAngles[PITCH] > 180 ) {
|
|
dest = (-360 + headAngles[PITCH]) * 0.75f;
|
|
} else {
|
|
dest = headAngles[PITCH] * 0.75f;
|
|
}
|
|
CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
|
|
torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
|
|
|
|
//
|
|
clientNum = cent->currentState.clientNum;
|
|
if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
if ( ci->fixedtorso ) {
|
|
torsoAngles[PITCH] = 0.0f;
|
|
}
|
|
}
|
|
|
|
// --------- roll -------------
|
|
|
|
|
|
// lean towards the direction of travel
|
|
VectorCopy( cent->currentState.pos.trDelta, velocity );
|
|
speed = VectorNormalize( velocity );
|
|
if ( speed ) {
|
|
vec3_t axis[3];
|
|
float side;
|
|
|
|
speed *= 0.05f;
|
|
|
|
AnglesToAxis( legsAngles, axis );
|
|
side = speed * DotProduct( velocity, axis[1] );
|
|
legsAngles[ROLL] -= side;
|
|
|
|
side = speed * DotProduct( velocity, axis[0] );
|
|
legsAngles[PITCH] += side;
|
|
}
|
|
|
|
//
|
|
clientNum = cent->currentState.clientNum;
|
|
if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
if ( ci->fixedlegs ) {
|
|
legsAngles[YAW] = torsoAngles[YAW];
|
|
legsAngles[PITCH] = 0.0f;
|
|
legsAngles[ROLL] = 0.0f;
|
|
}
|
|
}
|
|
|
|
// pain twitch
|
|
CG_AddPainTwitch( cent, torsoAngles );
|
|
|
|
// pull the angles back out of the hierarchial chain
|
|
AnglesSubtract( headAngles, torsoAngles, headAngles );
|
|
AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
|
|
AnglesToAxis( legsAngles, legs );
|
|
AnglesToAxis( torsoAngles, torso );
|
|
AnglesToAxis( headAngles, head );
|
|
}
|
|
|
|
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;
|
|
|
|
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
|
|
//Unfortunately the above doesn't really work.
|
|
trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList,
|
|
blendTime, currentTime);
|
|
}
|
|
|
|
void CG_G2ClientSpineAngles( centity_t *cent, vec3_t viewAngles, const vec3_t angles, vec3_t thoracicAngles, vec3_t ulAngles, vec3_t llAngles )
|
|
{
|
|
int ang = 0;
|
|
|
|
if (cent->isATST || cent->currentState.teamowner)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorClear(ulAngles);
|
|
VectorClear(llAngles);
|
|
|
|
//cent->pe.torso.pitchAngle = viewAngles[PITCH];
|
|
viewAngles[YAW] = AngleDelta( cent->lerpAngles[YAW], angles[YAW] );
|
|
//cent->pe.torso.yawAngle = viewAngles[YAW];
|
|
|
|
if ( !BG_FlippingAnim( cent->currentState.legsAnim ) &&
|
|
!BG_SpinningSaberAnim( cent->currentState.legsAnim ) &&
|
|
!BG_SpinningSaberAnim( cent->currentState.torsoAnim ) &&
|
|
!BG_InSpecialJump( cent->currentState.legsAnim ) &&
|
|
!BG_InSpecialJump( cent->currentState.torsoAnim ) &&
|
|
!CG_DeathAnim(cent->currentState.legsAnim) &&
|
|
!CG_DeathAnim(cent->currentState.torsoAnim) &&
|
|
!CG_InRoll(cent) &&
|
|
!PM_SaberInSpecial(cent->currentState.saberMove) &&
|
|
!PM_SaberInSpecialAttack(cent->currentState.torsoAnim) &&
|
|
!PM_SaberInSpecialAttack(cent->currentState.legsAnim) &&
|
|
|
|
/*
|
|
!BG_FlippingAnim( cent->rootBone ) &&
|
|
!BG_SpinningSaberAnim( cent->rootBone ) &&
|
|
!BG_InSpecialJump( cent->rootBone ) &&
|
|
!CG_DeathAnim(cent->rootBone) &&
|
|
!PM_SaberInSpecialAttack(cent->rootBone) &&
|
|
*/
|
|
|
|
!(cent->currentState.eFlags & EF_DEAD) )
|
|
{
|
|
//adjust for motion offset
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t motionFwd, motionAngles;
|
|
|
|
//trap_G2API_GetBoltMatrix( cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].bolt_motion, &boltMatrix, vec3_origin, cent->lerpOrigin, cg.time, /*cgs.gameModels*/0, cent->modelScale);
|
|
trap_G2API_GetBoltMatrix_NoReconstruct( cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].bolt_motion, &boltMatrix, vec3_origin, cent->lerpOrigin, cg.time, /*cgs.gameModels*/0, cent->modelScale);
|
|
trap_G2API_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, motionFwd );
|
|
|
|
vectoangles( motionFwd, motionAngles );
|
|
for ( ang = 0; ang < 3; ang++ )
|
|
{
|
|
viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( motionAngles[ang] ) );
|
|
}
|
|
}
|
|
//distribute the angles differently up the spine
|
|
//NOTE: each of these distributions must add up to 1.0f
|
|
thoracicAngles[PITCH] = viewAngles[PITCH]*0.20f;
|
|
llAngles[PITCH] = viewAngles[PITCH]*0.40f;
|
|
ulAngles[PITCH] = viewAngles[PITCH]*0.40f;
|
|
|
|
// thoracicAngles[YAW] = viewAngles[YAW]*0.20f;
|
|
// ulAngles[YAW] = viewAngles[YAW]*0.35f;
|
|
// llAngles[YAW] = viewAngles[YAW]*0.45f;
|
|
|
|
thoracicAngles[YAW] = viewAngles[YAW]*0.20f;
|
|
ulAngles[YAW] = viewAngles[YAW]*0.25f;
|
|
llAngles[YAW] = viewAngles[YAW]*0.25f;
|
|
|
|
thoracicAngles[ROLL] = viewAngles[ROLL]*0.20f;
|
|
ulAngles[ROLL] = viewAngles[ROLL]*0.35f;
|
|
llAngles[ROLL] = viewAngles[ROLL]*0.45f;
|
|
|
|
for ( ang = 0; ang < 3; ang++ )
|
|
{
|
|
if (ulAngles[ang] < 0)
|
|
{
|
|
ulAngles[ang] += 360;
|
|
}
|
|
}
|
|
|
|
//thoracic is added modified again by neckAngle calculations, so don't set it until then
|
|
// BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
// BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
|
|
|
|
// trap_G2API_SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
// trap_G2API_SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
// trap_G2API_SetBoneAngles(cent->ghoul2, 0, "thoracic", thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
}
|
|
|
|
static void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t legsAngles){
|
|
vec3_t torsoAngles, headAngles;
|
|
float dest;
|
|
static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
|
|
vec3_t velocity;
|
|
float speed; //, speed_dif, speed_desired;
|
|
int dir;
|
|
vec3_t velPos, velAng;
|
|
int adddir = 0;
|
|
float dif;
|
|
float degrees_negative = 0;
|
|
float degrees_positive = 0;
|
|
vec3_t ulAngles, llAngles, viewAngles, angles, thoracicAngles = {0,0,0};
|
|
|
|
VectorCopy( cent->lerpAngles, headAngles );
|
|
headAngles[YAW] = AngleMod( headAngles[YAW] );
|
|
VectorClear( legsAngles );
|
|
VectorClear( torsoAngles );
|
|
|
|
// --------- yaw -------------
|
|
|
|
// allow yaw to drift a bit
|
|
if ((( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_STAND1) ||
|
|
( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != WeaponReadyAnim[cent->currentState.weapon] )
|
|
{
|
|
// if not standing still, always point all in the same direction
|
|
cent->pe.torso.yawing = qtrue; // always center
|
|
cent->pe.torso.pitching = qtrue; // always center
|
|
cent->pe.legs.yawing = qtrue; // always center
|
|
}
|
|
|
|
// adjust legs for movement dir
|
|
if ( cent->currentState.eFlags & EF_DEAD ) {
|
|
// don't let dead bodies twitch
|
|
dir = 0;
|
|
} else {
|
|
dir = cent->currentState.angles2[YAW];
|
|
if ( dir < 0 || dir > 7 ) {
|
|
CG_Error( "Bad player movement angle" );
|
|
}
|
|
}
|
|
if (!cent->isATST)
|
|
{
|
|
torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ];
|
|
}
|
|
else
|
|
{
|
|
torsoAngles[YAW] = headAngles[YAW];
|
|
}
|
|
|
|
// torso
|
|
// CG_SwingAngles( torsoAngles[YAW], 25, 90, /*cg_swingSpeed.value*/ 0.3, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing );
|
|
// torsoAngles[YAW] = cent->pe.torso.yawAngle;
|
|
|
|
//for now, turn torso instantly and let the legs swing to follow
|
|
cent->pe.torso.yawAngle = torsoAngles[YAW];
|
|
|
|
// --------- pitch -------------
|
|
|
|
VectorCopy( cent->currentState.pos.trDelta, velocity );
|
|
speed = VectorNormalize( velocity );
|
|
|
|
if (!speed)
|
|
{
|
|
torsoAngles[YAW] = headAngles[YAW];
|
|
}
|
|
|
|
// only show a fraction of the pitch angle in the torso
|
|
if ( headAngles[PITCH] > 180 ) {
|
|
dest = (-360 + headAngles[PITCH]) * 0.75;
|
|
} else {
|
|
dest = headAngles[PITCH] * 0.75;
|
|
}
|
|
CG_SwingAngles( dest, 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
|
|
torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
|
|
|
|
// --------- roll -------------
|
|
|
|
|
|
// lean towards the direction of travel
|
|
|
|
/*
|
|
speed_desired = cent->currentState.speed/4;
|
|
|
|
if (!speed)
|
|
{
|
|
speed_dif = 0;
|
|
}
|
|
else
|
|
{
|
|
speed_dif = (speed/speed_desired);
|
|
}
|
|
|
|
if (speed_dif > 1)
|
|
{
|
|
speed_dif = 1;
|
|
}
|
|
else if (speed_dif < 0)
|
|
{
|
|
speed_dif = 0;
|
|
}
|
|
*/
|
|
if ( speed ) {
|
|
vec3_t axis[3];
|
|
float side;
|
|
|
|
speed *= 0.05;
|
|
|
|
AnglesToAxis( legsAngles, axis );
|
|
side = speed * DotProduct( velocity, axis[1] );
|
|
legsAngles[ROLL] -= side;
|
|
|
|
side = speed * DotProduct( velocity, axis[0] );
|
|
legsAngles[PITCH] += side;
|
|
}
|
|
|
|
//legsAngles[YAW] = headAngles[YAW] + (movementOffsets[ dir ]*speed_dif);
|
|
|
|
//rww - crazy velocity-based leg angle calculation
|
|
legsAngles[YAW] = headAngles[YAW];
|
|
velPos[0] = cent->lerpOrigin[0] + velocity[0];
|
|
velPos[1] = cent->lerpOrigin[1] + velocity[1];
|
|
velPos[2] = cent->lerpOrigin[2];// + velocity[2];
|
|
VectorSubtract(cent->lerpOrigin, velPos, velAng);
|
|
|
|
if (!VectorCompare(velAng, vec3_origin))
|
|
{
|
|
vectoangles(velAng, velAng);
|
|
|
|
if (velAng[YAW] <= legsAngles[YAW])
|
|
{
|
|
degrees_negative = (legsAngles[YAW] - velAng[YAW]);
|
|
degrees_positive = (360 - legsAngles[YAW]) + velAng[YAW];
|
|
}
|
|
else
|
|
{
|
|
degrees_negative = legsAngles[YAW] + (360 - velAng[YAW]);
|
|
degrees_positive = (velAng[YAW] - legsAngles[YAW]);
|
|
}
|
|
|
|
if (degrees_negative < degrees_positive)
|
|
{
|
|
dif = degrees_negative;
|
|
adddir = 0;
|
|
}
|
|
else
|
|
{
|
|
dif = degrees_positive;
|
|
adddir = 1;
|
|
}
|
|
|
|
if (dif > 90)
|
|
{
|
|
dif = (180 - dif);
|
|
}
|
|
|
|
if (cent->isATST)
|
|
{
|
|
if (dif > 360)
|
|
{
|
|
dif = 360;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dif > 60)
|
|
{
|
|
dif = 60;
|
|
}
|
|
}
|
|
|
|
//Slight hack for when playing is running backward
|
|
if (dir == 3 || dir == 5)
|
|
{
|
|
dif = -dif;
|
|
}
|
|
|
|
if (adddir)
|
|
{
|
|
legsAngles[YAW] -= dif;
|
|
}
|
|
else
|
|
{
|
|
legsAngles[YAW] += dif;
|
|
}
|
|
}
|
|
|
|
if (cent->isATST)
|
|
{
|
|
cent->pe.legs.yawSwingDif = CG_SwingAnglesATST( cent, legsAngles[YAW], 40, 360, /*cg_swingSpeed.value*/ 0.05, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
|
|
}
|
|
else
|
|
{
|
|
CG_SwingAngles( legsAngles[YAW], 40, 90, /*cg_swingSpeed.value*/ 0.3, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
|
|
}
|
|
legsAngles[YAW] = cent->pe.legs.yawAngle;
|
|
|
|
// pain twitch
|
|
CG_AddPainTwitch( cent, torsoAngles );
|
|
|
|
legsAngles[ROLL] = 0;
|
|
torsoAngles[ROLL] = 0;
|
|
|
|
VectorCopy(legsAngles, cent->turAngles);
|
|
|
|
if (cent->isATST)
|
|
{
|
|
legsAngles[ROLL] = 0;
|
|
legsAngles[PITCH] = 0;
|
|
}
|
|
|
|
// pull the angles back out of the hierarchial chain
|
|
AnglesSubtract( headAngles, torsoAngles, headAngles );
|
|
AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
|
|
AnglesToAxis( legsAngles, legs );
|
|
// we assume that model 0 is the player model.
|
|
|
|
if (cent->isATST)
|
|
{
|
|
vec3_t flatYaw;
|
|
|
|
flatYaw[YAW] = 0;//cent->lerpAngles[YAW];
|
|
flatYaw[ROLL] = 0;
|
|
flatYaw[PITCH] = 0;
|
|
CG_G2SetBoneAngles(cent->ghoul2, 0, "pelvis", flatYaw, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
|
|
CG_G2SetBoneAngles(cent->ghoul2, 0, "thoracic", torsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
|
|
return;
|
|
}
|
|
|
|
// trap_G2API_SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", torsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
|
|
VectorCopy( cent->lerpAngles, viewAngles );
|
|
viewAngles[YAW] = viewAngles[ROLL] = 0;
|
|
viewAngles[PITCH] *= 0.5;
|
|
|
|
VectorCopy( cent->lerpAngles, angles );
|
|
angles[PITCH] = 0;
|
|
|
|
CG_G2ClientSpineAngles(cent, viewAngles, angles, thoracicAngles, ulAngles, llAngles);
|
|
|
|
ulAngles[YAW] += torsoAngles[YAW]*0.3;
|
|
llAngles[YAW] += torsoAngles[YAW]*0.3;
|
|
thoracicAngles[YAW] += torsoAngles[YAW]*0.4;
|
|
|
|
ulAngles[PITCH] = torsoAngles[PITCH]*0.3;
|
|
llAngles[PITCH] = torsoAngles[PITCH]*0.3;
|
|
thoracicAngles[PITCH] = torsoAngles[PITCH]*0.4;
|
|
|
|
ulAngles[ROLL] += torsoAngles[ROLL]*0.3;
|
|
llAngles[ROLL] += torsoAngles[ROLL]*0.3;
|
|
thoracicAngles[ROLL] += torsoAngles[ROLL]*0.4;
|
|
|
|
//We must compensate yaw for pitch so that the player can't bend all the way over and contort his spine
|
|
//to the left/right at an unnatural angle
|
|
if (ulAngles[PITCH] > 0)
|
|
{
|
|
if (ulAngles[YAW] > 0)
|
|
{
|
|
ulAngles[YAW] -= ulAngles[PITCH];
|
|
if (ulAngles[YAW] < 0)
|
|
{
|
|
ulAngles[YAW] = 0;
|
|
}
|
|
}
|
|
else if (ulAngles[YAW] < 0)
|
|
{
|
|
ulAngles[YAW] += ulAngles[PITCH];
|
|
if (ulAngles[YAW] > 0)
|
|
{
|
|
ulAngles[YAW] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (llAngles[PITCH] > 0)
|
|
{
|
|
if (llAngles[YAW] > 0)
|
|
{
|
|
llAngles[YAW] -= llAngles[PITCH];
|
|
if (llAngles[YAW] < 0)
|
|
{
|
|
llAngles[YAW] = 0;
|
|
}
|
|
}
|
|
else if (llAngles[YAW] < 0)
|
|
{
|
|
llAngles[YAW] += llAngles[PITCH];
|
|
if (llAngles[YAW] > 0)
|
|
{
|
|
llAngles[YAW] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (thoracicAngles[PITCH] > 0)
|
|
{
|
|
if (thoracicAngles[YAW] > 0)
|
|
{
|
|
thoracicAngles[YAW] -= thoracicAngles[PITCH];
|
|
if (thoracicAngles[YAW] < 0)
|
|
{
|
|
thoracicAngles[YAW] = 0;
|
|
}
|
|
}
|
|
else if (thoracicAngles[YAW] < 0)
|
|
{
|
|
thoracicAngles[YAW] += thoracicAngles[PITCH];
|
|
if (thoracicAngles[YAW] > 0)
|
|
{
|
|
thoracicAngles[YAW] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( cent->currentState.otherEntityNum2 && !(cent->currentState.eFlags & EF_DEAD) )
|
|
{ //using an emplaced gun
|
|
centity_t *empEnt = &cg_entities[cent->currentState.otherEntityNum2];
|
|
vec3_t headAng;
|
|
|
|
VectorClear(headAng);
|
|
|
|
if (empEnt)
|
|
{
|
|
vec3_t dif, empOrg, originalAngle;
|
|
|
|
VectorCopy(cent->lerpAngles, originalAngle);
|
|
|
|
VectorCopy(empEnt->lerpOrigin, empOrg);
|
|
|
|
empOrg[2] += 32;
|
|
|
|
VectorSubtract(empOrg, cent->lerpOrigin, dif);
|
|
VectorNormalize(dif);
|
|
vectoangles(dif, dif);
|
|
|
|
VectorCopy(dif, cent->lerpAngles);
|
|
|
|
VectorCopy(cent->lerpAngles, legsAngles);
|
|
|
|
VectorSubtract(originalAngle, cent->lerpAngles, headAng);
|
|
|
|
headAng[PITCH] /= 3; //scale it down so that you can't bend your neck all the way back
|
|
}
|
|
else
|
|
{
|
|
VectorClear(cent->lerpAngles);
|
|
VectorClear(legsAngles);
|
|
}
|
|
|
|
AnglesToAxis( legsAngles, legs );
|
|
|
|
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", headAng, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
}
|
|
else
|
|
{
|
|
CG_G2SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
CG_G2SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time);
|
|
CG_G2SetBoneAngles(cent->ghoul2, 0, "thoracic", thoracicAngles, 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);
|
|
}
|
|
|
|
//trap_G2API_SetBoneAngles(cent->ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels, 0, cg.time);
|
|
}
|
|
//==========================================================================
|
|
|
|
/*
|
|
===============
|
|
CG_HasteTrail
|
|
===============
|
|
*/
|
|
static void CG_HasteTrail( centity_t *cent ) {
|
|
localEntity_t *smoke;
|
|
vec3_t origin;
|
|
int anim;
|
|
|
|
if ( cent->trailTime > cg.time ) {
|
|
return;
|
|
}
|
|
anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT;
|
|
if ( anim != BOTH_RUN1 && anim != BOTH_RUNBACK1 ) {
|
|
return;
|
|
}
|
|
|
|
cent->trailTime += 100;
|
|
if ( cent->trailTime < cg.time ) {
|
|
cent->trailTime = cg.time;
|
|
}
|
|
|
|
VectorCopy( cent->lerpOrigin, origin );
|
|
origin[2] -= 16;
|
|
|
|
smoke = CG_SmokePuff( origin, vec3_origin,
|
|
8,
|
|
1, 1, 1, 1,
|
|
500,
|
|
cg.time,
|
|
0,
|
|
0,
|
|
cgs.media.hastePuffShader );
|
|
|
|
// use the optimized local entity add
|
|
smoke->leType = LE_SCALE_FADE;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_BreathPuffs
|
|
===============
|
|
*/
|
|
static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) {
|
|
clientInfo_t *ci;
|
|
vec3_t up, origin;
|
|
int contents;
|
|
|
|
// ci = &cgs.clientinfo[ cent->currentState.number ]; // NOTENOTE number was id's code, did they mean clientNum?
|
|
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
|
|
|
|
if (!cg_enableBreath.integer) {
|
|
return;
|
|
}
|
|
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) {
|
|
return;
|
|
}
|
|
if ( cent->currentState.eFlags & EF_DEAD ) {
|
|
return;
|
|
}
|
|
contents = trap_CM_PointContents( head->origin, 0 );
|
|
if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
|
|
return;
|
|
}
|
|
if ( ci->breathPuffTime > cg.time ) {
|
|
return;
|
|
}
|
|
|
|
VectorSet( up, 0, 0, 8 );
|
|
VectorMA(head->origin, 8, head->axis[0], origin);
|
|
VectorMA(origin, -4, head->axis[2], origin);
|
|
CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader );
|
|
ci->breathPuffTime = cg.time + 2000;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_DustTrail
|
|
===============
|
|
*/
|
|
/*
|
|
static void CG_DustTrail( centity_t *cent ) {
|
|
int anim;
|
|
localEntity_t *dust;
|
|
vec3_t end, vel;
|
|
trace_t tr;
|
|
|
|
if (!cg_enableDust.integer)
|
|
return;
|
|
|
|
if ( cent->dustTrailTime > cg.time ) {
|
|
return;
|
|
}
|
|
|
|
anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT;
|
|
if ( anim != LEGS_LANDB && anim != LEGS_LAND ) {
|
|
return;
|
|
}
|
|
|
|
cent->dustTrailTime += 40;
|
|
if ( cent->dustTrailTime < cg.time ) {
|
|
cent->dustTrailTime = cg.time;
|
|
}
|
|
|
|
VectorCopy(cent->currentState.pos.trBase, end);
|
|
end[2] -= 64;
|
|
CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID );
|
|
|
|
if ( !(tr.surfaceFlags & SURF_DUST) )
|
|
return;
|
|
|
|
VectorCopy( cent->currentState.pos.trBase, end );
|
|
end[2] -= 16;
|
|
|
|
VectorSet(vel, 0, 0, -30);
|
|
dust = CG_SmokePuff( end, vel,
|
|
24,
|
|
.8f, .8f, 0.7f, 0.33f,
|
|
500,
|
|
cg.time,
|
|
0,
|
|
0,
|
|
cgs.media.dustPuffShader );
|
|
}
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
CG_TrailItem
|
|
===============
|
|
*/
|
|
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 );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerFlag
|
|
===============
|
|
*/
|
|
static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) {
|
|
#if 0
|
|
clientInfo_t *ci;
|
|
refEntity_t pole;
|
|
refEntity_t flag;
|
|
vec3_t angles, dir;
|
|
int legsAnim, /*flagAnim,*/ updateangles;
|
|
float angle, d;
|
|
|
|
// show the flag pole model
|
|
memset( &pole, 0, sizeof(pole) );
|
|
pole.hModel = cgs.media.flagPoleModel;
|
|
VectorCopy( torso->lightingOrigin, pole.lightingOrigin );
|
|
pole.shadowPlane = torso->shadowPlane;
|
|
pole.renderfx = torso->renderfx;
|
|
CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" );
|
|
trap_R_AddRefEntityToScene( &pole );
|
|
|
|
// show the flag model
|
|
memset( &flag, 0, sizeof(flag) );
|
|
flag.hModel = cgs.media.flagFlapModel;
|
|
flag.customSkin = hSkin;
|
|
VectorCopy( torso->lightingOrigin, flag.lightingOrigin );
|
|
flag.shadowPlane = torso->shadowPlane;
|
|
flag.renderfx = torso->renderfx;
|
|
|
|
VectorClear(angles);
|
|
|
|
updateangles = qfalse;
|
|
legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
|
|
/*
|
|
if( legsAnim == TORSO_WEAPONREADY3 || legsAnim == BOTH_CROUCH1IDLE ) {
|
|
flagAnim = FLAG_STAND;
|
|
} else if ( legsAnim == BOTH_WALK1 || legsAnim == BOTH_CROUCH1WALK ) {
|
|
flagAnim = FLAG_STAND;
|
|
updateangles = qtrue;
|
|
} else {
|
|
flagAnim = FLAG_RUN;
|
|
updateangles = qtrue;
|
|
}
|
|
*/
|
|
if ( updateangles ) {
|
|
|
|
VectorCopy( cent->currentState.pos.trDelta, dir );
|
|
// add gravity
|
|
dir[2] += 100;
|
|
VectorNormalize( dir );
|
|
d = DotProduct(pole.axis[2], dir);
|
|
// if there is anough movement orthogonal to the flag pole
|
|
if (fabs(d) < 0.9) {
|
|
//
|
|
d = DotProduct(pole.axis[0], dir);
|
|
if (d > 1.0f) {
|
|
d = 1.0f;
|
|
}
|
|
else if (d < -1.0f) {
|
|
d = -1.0f;
|
|
}
|
|
angle = acos(d);
|
|
|
|
d = DotProduct(pole.axis[1], dir);
|
|
if (d < 0) {
|
|
angles[YAW] = 360 - angle * 180 / M_PI;
|
|
}
|
|
else {
|
|
angles[YAW] = angle * 180 / M_PI;
|
|
}
|
|
if (angles[YAW] < 0)
|
|
angles[YAW] += 360;
|
|
if (angles[YAW] > 360)
|
|
angles[YAW] -= 360;
|
|
|
|
//vectoangles( cent->currentState.pos.trDelta, tmpangles );
|
|
//angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle;
|
|
// change the yaw angle
|
|
CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing );
|
|
}
|
|
|
|
/*
|
|
d = DotProduct(pole.axis[2], dir);
|
|
angle = Q_acos(d);
|
|
|
|
d = DotProduct(pole.axis[1], dir);
|
|
if (d < 0) {
|
|
angle = 360 - angle * 180 / M_PI;
|
|
}
|
|
else {
|
|
angle = angle * 180 / M_PI;
|
|
}
|
|
if (angle > 340 && angle < 20) {
|
|
flagAnim = FLAG_RUNUP;
|
|
}
|
|
if (angle > 160 && angle < 200) {
|
|
flagAnim = FLAG_RUNDOWN;
|
|
}
|
|
*/
|
|
}
|
|
|
|
// set the yaw angle
|
|
angles[YAW] = cent->pe.flag.yawAngle;
|
|
// lerp the flag animation frames
|
|
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
|
|
// CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 );
|
|
flag.oldframe = cent->pe.flag.oldFrame;
|
|
flag.frame = cent->pe.flag.frame;
|
|
flag.backlerp = cent->pe.flag.backlerp;
|
|
|
|
AnglesToAxis( angles, flag.axis );
|
|
CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" );
|
|
|
|
trap_R_AddRefEntityToScene( &flag );
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
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 );
|
|
}
|
|
|
|
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
|
|
// redflag
|
|
if ( powerups & ( 1 << PW_REDFLAG ) ) {
|
|
if (ci->newAnims) {
|
|
CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso );
|
|
}
|
|
else {
|
|
CG_TrailItem( cent, cgs.media.redFlagModel );
|
|
}
|
|
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f );
|
|
}
|
|
|
|
// blueflag
|
|
if ( powerups & ( 1 << PW_BLUEFLAG ) ) {
|
|
if (ci->newAnims){
|
|
CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso );
|
|
}
|
|
else {
|
|
CG_TrailItem( cent, cgs.media.blueFlagModel );
|
|
}
|
|
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 );
|
|
}
|
|
|
|
// neutralflag
|
|
if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) {
|
|
if (ci->newAnims) {
|
|
CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso );
|
|
}
|
|
else {
|
|
CG_TrailItem( cent, cgs.media.neutralFlagModel );
|
|
}
|
|
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
|
|
===============
|
|
*/
|
|
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 );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_PlayerSprites
|
|
|
|
Float sprites over the player's head
|
|
===============
|
|
*/
|
|
static void CG_PlayerSprites( centity_t *cent ) {
|
|
#if 0
|
|
int team;
|
|
|
|
if ( cent->currentState.eFlags & EF_CONNECTION ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.connectionShader );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_TALK ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.balloonShader );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.medalImpressive );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.medalExcellent );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.medalDefend );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.medalAssist );
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.eFlags & EF_AWARD_CAP ) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.medalCapture );
|
|
return;
|
|
}
|
|
|
|
team = cgs.clientinfo[ cent->currentState.clientNum ].team;
|
|
if ( !(cent->currentState.eFlags & EF_DEAD) &&
|
|
cg.snap->ps.persistant[PERS_TEAM] == team &&
|
|
cgs.gametype >= GT_TEAM) {
|
|
if (cg_drawFriend.integer) {
|
|
CG_PlayerFloatSprite( cent, cgs.media.friendShader );
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
|
|
*shadowPlane = 0;
|
|
|
|
if ( cg_shadows.integer == 0 ) {
|
|
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
|
|
}
|
|
|
|
// send a trace down from the player to the ground
|
|
VectorCopy( cent->lerpOrigin, end );
|
|
end[2] -= SHADOW_DISTANCE;
|
|
|
|
trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
|
|
|
|
// no shadow if too high
|
|
if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) {
|
|
return qfalse;
|
|
}
|
|
|
|
*shadowPlane = trace.endpos[2] + 1;
|
|
|
|
if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows
|
|
return qtrue;
|
|
}
|
|
|
|
// fade the shadow out with height
|
|
alpha = 1.0 - trace.fraction;
|
|
|
|
// 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
|
|
CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
|
|
cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, 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 );
|
|
}
|
|
|
|
void CG_ForcePushBlur( vec3_t org )
|
|
{
|
|
localEntity_t *ex;
|
|
|
|
ex = CG_AllocLocalEntity();
|
|
ex->leType = LE_PUFF;
|
|
ex->refEntity.reType = RT_SPRITE;
|
|
ex->radius = 2.0f;
|
|
ex->startTime = cg.time;
|
|
ex->endTime = ex->startTime + 120;
|
|
VectorCopy( org, ex->pos.trBase );
|
|
ex->pos.trTime = cg.time;
|
|
ex->pos.trType = TR_LINEAR;
|
|
VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta );
|
|
|
|
ex->color[0] = 24;
|
|
ex->color[1] = 32;
|
|
ex->color[2] = 40;
|
|
ex->refEntity.customShader = 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" );
|
|
}
|
|
|
|
static void CG_ForcePushBodyBlur( centity_t *cent, vec3_t origin, vec3_t tempAngles )
|
|
{
|
|
vec3_t eyePos;
|
|
|
|
VectorCopy(cent->lerpOrigin, eyePos);
|
|
eyePos[2] += 16;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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 );
|
|
|
|
if ( state->powerups & ( 1 << PW_QUAD ) )
|
|
{
|
|
if (team == TEAM_RED)
|
|
ent->customShader = cgs.media.redQuadShader;
|
|
else
|
|
ent->customShader = cgs.media.quadShader;
|
|
trap_R_AddRefEntityToScene( ent );
|
|
}
|
|
if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) {
|
|
ent->customShader = cgs.media.battleSuitShader;
|
|
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_CLIENTS)
|
|
{
|
|
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)
|
|
{
|
|
centity_t *curent;
|
|
|
|
// 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)
|
|
{
|
|
// Get the NON-PREDICTED player entity, because the predicted one doesn't have the damage info on it.
|
|
curent = &cg_entities[cent->currentState.number];
|
|
|
|
if (curent->damageTime > cg.time)
|
|
{
|
|
CG_DrawPlayerShield(curent, 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;
|
|
}
|
|
|
|
void CG_DoSaber( vec3_t origin, vec3_t dir, float length, saber_colors_t color, int rfx )
|
|
{
|
|
vec3_t mid, rgb={1,1,1};
|
|
qhandle_t blade = 0, glow = 0;
|
|
refEntity_t saber;
|
|
float radiusmult;
|
|
|
|
if ( length < 0.5f )
|
|
{
|
|
// if the thing is so short, just forget even adding me.
|
|
return;
|
|
}
|
|
|
|
// Find the midpoint of the saber for lighting purposes
|
|
VectorMA( origin, length * 0.5f, dir, mid );
|
|
|
|
switch( color )
|
|
{
|
|
case SABER_RED:
|
|
glow = cgs.media.redSaberGlowShader;
|
|
blade = cgs.media.redSaberCoreShader;
|
|
VectorSet( rgb, 1.0f, 0.2f, 0.2f );
|
|
break;
|
|
case SABER_ORANGE:
|
|
glow = cgs.media.orangeSaberGlowShader;
|
|
blade = cgs.media.orangeSaberCoreShader;
|
|
VectorSet( rgb, 1.0f, 0.5f, 0.1f );
|
|
break;
|
|
case SABER_YELLOW:
|
|
glow = cgs.media.yellowSaberGlowShader;
|
|
blade = cgs.media.yellowSaberCoreShader;
|
|
VectorSet( rgb, 1.0f, 1.0f, 0.2f );
|
|
break;
|
|
case SABER_GREEN:
|
|
glow = cgs.media.greenSaberGlowShader;
|
|
blade = cgs.media.greenSaberCoreShader;
|
|
VectorSet( rgb, 0.2f, 1.0f, 0.2f );
|
|
break;
|
|
case SABER_BLUE:
|
|
glow = cgs.media.blueSaberGlowShader;
|
|
blade = cgs.media.blueSaberCoreShader;
|
|
VectorSet( rgb, 0.2f, 0.4f, 1.0f );
|
|
break;
|
|
case SABER_PURPLE:
|
|
glow = cgs.media.purpleSaberGlowShader;
|
|
blade = cgs.media.purpleSaberCoreShader;
|
|
VectorSet( rgb, 0.9f, 0.2f, 1.0f );
|
|
break;
|
|
default:
|
|
glow = cgs.media.blueSaberGlowShader;
|
|
blade = cgs.media.blueSaberCoreShader;
|
|
VectorSet( rgb, 0.2f, 0.4f, 1.0f );
|
|
break;
|
|
}
|
|
|
|
// always add a light because sabers cast a nice glow before they slice you in half!! or something...
|
|
trap_R_AddLightToScene( mid, (length*2.0f) + (random()*8.0f), rgb[0], rgb[1], rgb[2] );
|
|
|
|
memset( &saber, 0, sizeof( refEntity_t ));
|
|
|
|
// Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many
|
|
// refEnts to do each glow blob individually
|
|
saber.saberLength = length;
|
|
|
|
// Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed.
|
|
// It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat
|
|
if (length < SABER_LENGTH_MAX)
|
|
{
|
|
radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5.
|
|
}
|
|
else
|
|
{
|
|
radiusmult = 1.0;
|
|
}
|
|
|
|
|
|
saber.radius = (2.8 + 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;
|
|
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.3f + random() * 0.3f;
|
|
|
|
VectorSubtract( end, start, axis[1] );
|
|
|
|
// We probably don't want to create a ton of small marks, so bias towards doing larger ones
|
|
if ( 1)// VectorNormalize( axis[1] ) > 1.0f )// || rand() & 3 )
|
|
{
|
|
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] * 1.3f;
|
|
originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i] * 1.3f;
|
|
originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i] * 1.3f;
|
|
originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i] * 1.3f;
|
|
}
|
|
|
|
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 );
|
|
|
|
// colors[0] = colors[1] = colors[2] = colors[3] = 255;
|
|
|
|
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.15f + crandom() * 0.14f);
|
|
v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.1f + crandom() * 0.05f);
|
|
// *(int *)v->modulate = *(int *)colors;
|
|
}
|
|
|
|
// 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] ) );
|
|
|
|
//FIXME: This part looks totally messed up. I don't know exactly what's causing it.
|
|
|
|
// 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 = trap_R_RegisterShader("gfx/effects/saberDamageGlow" );
|
|
mark->poly.numVerts = mf->numPoints;
|
|
mark->color[0] = 255;
|
|
mark->color[1] = 130 + sin( cg.time * 0.03f ) * 36.0f;//80 + random() * 96.0f;
|
|
mark->color[2] = 10;
|
|
mark->color[3] = 10;//= random()*15.0f;
|
|
memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SABER_TRAIL_TIME 40.0f
|
|
#define FX_USE_ALPHA 0x08000000
|
|
|
|
void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, vec3_t origin, vec3_t angles, qboolean fromSaber)
|
|
{
|
|
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;
|
|
float saberLen, dualSaberLen;
|
|
float diff;
|
|
clientInfo_t *client;
|
|
centity_t *saberEnt;
|
|
saberTrail_t *saberTrail;
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t futureAngles;
|
|
effectTrailArgStruct_t fx;
|
|
int scolor = 0;
|
|
vec3_t otherPos, otherDir, otherEnd;
|
|
float dualLen = 0.7;
|
|
|
|
saberEnt = &cg_entities[cent->currentState.saberEntityNum];
|
|
|
|
if (/*cg.snap->ps.clientNum == cent->currentState.number && */
|
|
cgs.clientinfo[ cent->currentState.clientNum ].team != TEAM_SPECTATOR &&
|
|
!(cg.snap->ps.pm_flags & PMF_FOLLOW))
|
|
{
|
|
if (cent->saberLength < 1)
|
|
{
|
|
cent->saberLength = 1;
|
|
cent->saberExtendTime = cg.time;
|
|
}
|
|
|
|
if (cent->saberLength < SABER_LENGTH_MAX)
|
|
{
|
|
cent->saberLength += (cg.time - cent->saberExtendTime)*0.05;
|
|
}
|
|
|
|
if (cent->saberLength > SABER_LENGTH_MAX)
|
|
{
|
|
cent->saberLength = SABER_LENGTH_MAX;
|
|
}
|
|
|
|
cent->saberExtendTime = cg.time;
|
|
saberLen = cent->saberLength;
|
|
}
|
|
else
|
|
{
|
|
saberLen = SABER_LENGTH_MAX;
|
|
}
|
|
|
|
/*
|
|
Ghoul2 Insert Start
|
|
*/
|
|
|
|
dualSaberLen = saberLen;
|
|
|
|
if ((cent->currentState.eFlags & EF_DEAD) && !fromSaber)
|
|
{ //trying to draw a saber on a corpse? That's bad.
|
|
return;
|
|
}
|
|
|
|
futureAngles[YAW] = angles[YAW];
|
|
futureAngles[PITCH] = angles[PITCH];
|
|
futureAngles[ROLL] = angles[ROLL];
|
|
|
|
// figure out where the actual model muzzle is
|
|
if (fromSaber)
|
|
{
|
|
trap_G2API_GetBoltMatrix(scent->ghoul2, 0, 0, &boltMatrix, futureAngles, origin, cg.time, cgs.gameModels, scent->modelScale);
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_GetBoltMatrix(scent->ghoul2, 1, 0, &boltMatrix, futureAngles, origin, cg.time, cgs.gameModels, scent->modelScale);
|
|
}
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, org_);
|
|
trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, axis_[0]);
|
|
|
|
if (!fromSaber && saberEnt)
|
|
{
|
|
VectorCopy(org_, saberEnt->currentState.pos.trBase);
|
|
|
|
VectorCopy(axis_[0], saberEnt->currentState.apos.trBase);
|
|
}
|
|
|
|
client = &cgs.clientinfo[cent->currentState.number];
|
|
|
|
if (!client)
|
|
{ //something horrible has apparently happened
|
|
return;
|
|
}
|
|
|
|
if (cent->currentState.bolt2)
|
|
{
|
|
VectorMA( org_, saberLen*dualLen, axis_[0], end );
|
|
}
|
|
else
|
|
{
|
|
VectorMA( org_, saberLen, axis_[0], end );
|
|
}
|
|
|
|
VectorAdd( end, axis_[0], end );
|
|
|
|
if (cent->currentState.bolt2)
|
|
{
|
|
otherPos[0] = org_[0] - axis_[0][0]*12;
|
|
otherPos[1] = org_[1] - axis_[0][1]*12;
|
|
otherPos[2] = org_[2] - axis_[0][2]*12;
|
|
|
|
otherDir[0] = -axis_[0][0];
|
|
otherDir[1] = -axis_[0][1];
|
|
otherDir[2] = -axis_[0][2];
|
|
|
|
VectorMA( otherPos, dualSaberLen*dualLen, otherDir, otherEnd );
|
|
VectorAdd( otherEnd, otherDir, otherEnd );
|
|
}
|
|
|
|
scolor = cgs.clientinfo[cent->currentState.number].icolor1;
|
|
|
|
if (cgs.gametype >= GT_TEAM)
|
|
{
|
|
if (cgs.clientinfo[cent->currentState.number].team == TEAM_RED)
|
|
{
|
|
scolor = SABER_RED;
|
|
}
|
|
else if (cgs.clientinfo[cent->currentState.number].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;
|
|
}
|
|
|
|
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 )
|
|
{
|
|
trap_FX_PlayEffectID( trap_FX_RegisterEffect("saber/spark.efx"), trace.endpos, trace.plane.normal );
|
|
|
|
//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 (cent->currentState.bolt2)
|
|
{
|
|
break;
|
|
}
|
|
// All I need is a bool to mark whether I have a previous point to work with.
|
|
//....come up with something better..
|
|
if ( client->saberTrail.haveOldPos[i] )
|
|
{
|
|
if ( trace.entityNum == ENTITYNUM_WORLD )
|
|
{//only put marks on architecture
|
|
// Let's do some cool burn/glowing mark bits!!!
|
|
CG_CreateSaberMarks( client->saberTrail.oldPos[i], trace.endpos, trace.plane.normal );
|
|
|
|
//make a sound
|
|
if ( cg.time - client->saberHitWallSoundDebounceTime >= 100 )
|
|
{//ugh, need to have a real sound debouncer... or do this game-side
|
|
client->saberHitWallSoundDebounceTime = cg.time;
|
|
trap_S_StartSound ( trace.endpos, -1, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/saber/saberhitwall.wav" ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we impact next frame, we'll mark a slash mark
|
|
client->saberTrail.haveOldPos[i] = qtrue;
|
|
// CG_ImpactMark( cgs.media.rivetMarkShader, client->saberTrail.oldPos[i], client->saberTrail.oldNormal[i],
|
|
// 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse );
|
|
}
|
|
|
|
// stash point so we can connect-the-dots later
|
|
VectorCopy( trace.endpos, client->saberTrail.oldPos[i] );
|
|
VectorCopy( trace.plane.normal, client->saberTrail.oldNormal[i] );
|
|
}
|
|
else
|
|
{
|
|
if (cent->currentState.bolt2)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( client->saberTrail.haveOldPos[i] )
|
|
{
|
|
// Hmmm, no impact this frame, but we have an old point
|
|
// Let's put the mark there, we should use an endcap mark to close the line, but we
|
|
// can probably just get away with a round mark
|
|
// CG_ImpactMark( cgs.media.rivetMarkShader, client->saberTrail.oldPos[i], client->saberTrail.oldNormal[i],
|
|
// 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse );
|
|
}
|
|
|
|
// we aren't impacting, so turn off our mark tracking mechanism
|
|
client->saberTrail.haveOldPos[i] = qfalse;
|
|
}
|
|
}
|
|
|
|
if (cent->currentState.bolt2)
|
|
{
|
|
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
|
|
{
|
|
CG_Trace( &trace, otherPos, NULL, NULL, otherEnd, ENTITYNUM_NONE, MASK_SOLID );
|
|
|
|
if ( trace.fraction < 1.0f )
|
|
{
|
|
trap_FX_PlayEffectID( trap_FX_RegisterEffect("saber/spark.efx"), trace.endpos, trace.plane.normal );
|
|
|
|
//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(otherPos, trace.endpos, v);
|
|
dualSaberLen = VectorLength(v);
|
|
|
|
VectorCopy(trace.endpos, end);
|
|
}
|
|
}
|
|
}
|
|
CheckTrail:
|
|
|
|
if (!cg_saberTrail.integer)
|
|
{ //don't do the trail in this case
|
|
goto JustDoIt;
|
|
}
|
|
|
|
saberTrail = &client->saberTrail;
|
|
|
|
// 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 ) // 2ms
|
|
{
|
|
if ( (saberMoveData[cent->currentState.saberMove].trailLength > 0 || ((cent->currentState.powerups & (1 << PW_SPEED) && cg_speedTrail.integer)) || cent->currentState.saberInFlight) && cg.time < saberTrail->lastTime + 2000 ) // if we have a stale segment, don't draw until we have a fresh one
|
|
{
|
|
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 )
|
|
{
|
|
float oldAlpha = 1.0f - ( diff / SABER_TRAIL_TIME );
|
|
|
|
// 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;
|
|
|
|
fx.mShader = cgs.media.saberBlurShader;
|
|
fx.mSetFlags = FX_USE_ALPHA;
|
|
fx.mKillTime = SABER_TRAIL_TIME;
|
|
|
|
trap_FX_AddPrimitive(&fx);
|
|
}
|
|
|
|
if (cent->currentState.bolt2)
|
|
{
|
|
float oldAlpha = 1.0f - ( diff / SABER_TRAIL_TIME );
|
|
|
|
VectorCopy( otherPos, fx.mVerts[0].origin );
|
|
VectorMA( otherEnd, 3.0f, otherDir, fx.mVerts[1].origin );
|
|
|
|
VectorCopy( saberTrail->dualtip, fx.mVerts[2].origin );
|
|
VectorCopy( saberTrail->dualbase, fx.mVerts[3].origin );
|
|
|
|
// New muzzle
|
|
VectorCopy( rgb1, fx.mVerts[0].rgb );
|
|
fx.mVerts[0].alpha = 255.0f;
|
|
|
|
fx.mVerts[0].ST[0] = 0.0f;
|
|
fx.mVerts[0].ST[1] = 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;
|
|
|
|
fx.mShader = cgs.media.saberBlurShader;
|
|
fx.mSetFlags = FX_USE_ALPHA;
|
|
fx.mKillTime = SABER_TRAIL_TIME;
|
|
|
|
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;
|
|
|
|
if (cent->currentState.bolt2)
|
|
{
|
|
VectorCopy( otherPos, saberTrail->dualbase );
|
|
VectorMA( otherEnd, 3.0f, otherDir, saberTrail->dualtip );
|
|
}
|
|
}
|
|
|
|
JustDoIt:
|
|
|
|
if (client && cent->currentState.bolt2)
|
|
{
|
|
float sideOneLen = saberLen*dualLen;
|
|
float sideTwoLen = dualSaberLen*dualLen;
|
|
|
|
if (sideOneLen < 1)
|
|
{
|
|
sideOneLen = 1;
|
|
}
|
|
|
|
CG_DoSaber( org_, axis_[0], sideOneLen, scolor, renderfx );
|
|
|
|
CG_DoSaber( otherPos, otherDir, sideTwoLen, scolor, renderfx );
|
|
}
|
|
else
|
|
{
|
|
// 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, scolor, renderfx );
|
|
}
|
|
}
|
|
|
|
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_DrawYsalimariSphere(centity_t *cent, vec3_t origin, vec3_t colors)
|
|
{
|
|
refEntity_t ent;
|
|
int alpha;
|
|
float scale;
|
|
vec3_t sphereAngles;
|
|
float addSum = 0;
|
|
|
|
// 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] -= 32.0;
|
|
|
|
VectorCopy(cent->lerpAngles, sphereAngles);
|
|
sphereAngles[PITCH] = 0;
|
|
|
|
AnglesToAxis( sphereAngles, ent.axis );
|
|
|
|
alpha = 255.0 * ((100) / 1750.0) + Q_irand(0, 16);
|
|
if (alpha>255)
|
|
alpha=255;
|
|
|
|
//Slowly pulse the scale up and down
|
|
|
|
addSum = (((cg.time / 12) & 255) * (M_PI * 2) / 255)/8;
|
|
|
|
if (addSum >= 0.4)
|
|
{
|
|
addSum = (0.4 - (addSum - 0.4));
|
|
}
|
|
scale = 0.9;
|
|
|
|
addSum *= 0.2;
|
|
|
|
scale += addSum;
|
|
|
|
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 = trap_R_RegisterModel ( "models/map_objects/mp/sphere.md3" );//cgs.media.explosionModel;
|
|
ent.customShader = trap_R_RegisterShader( "powerups/ysalimarishell" );//cgs.media.halfShieldShader;
|
|
ent.renderfx = (RF_RGB_TINT|RF_FORCE_ENT_ALPHA);
|
|
if (colors[0] != -1)
|
|
{
|
|
ent.shaderRGBA[0] = colors[0];
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[0] = alpha;
|
|
}
|
|
|
|
if (colors[1] != -1)
|
|
{
|
|
ent.shaderRGBA[1] = colors[1];
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[1] = alpha;
|
|
}
|
|
|
|
if (colors[2] != -1)
|
|
{
|
|
ent.shaderRGBA[2] = colors[2];
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[2] = alpha;
|
|
}
|
|
|
|
ent.shaderRGBA[3] = 20;//50;
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
|
|
//Add a second inner-core that pulses at a different speed
|
|
addSum = (((cg.time / 8) & 255) * (M_PI * 2) / 255)/8;
|
|
|
|
if (addSum >= 0.4)
|
|
{
|
|
addSum = (0.4 - (addSum - 0.4));
|
|
}
|
|
scale = 0.9;
|
|
|
|
addSum *= 0.2;
|
|
|
|
scale += addSum;
|
|
|
|
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] = 20;
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
void CG_DrawCustomSphere(centity_t *cent, vec3_t origin, vec3_t colors, float xScale, float yScale, float zScale, float timeScale, int custShader, int forceAlpha)
|
|
{
|
|
refEntity_t ent;
|
|
int alpha;
|
|
vec3_t sphereAngles;
|
|
float addSum = 0;
|
|
|
|
// 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] -= 32.0;
|
|
|
|
VectorCopy(cent->lerpAngles, sphereAngles);
|
|
sphereAngles[PITCH] = 0;
|
|
|
|
addSum = (((cg.time / 4) & 255) * (M_PI * 2) / 255)/6;
|
|
addSum *= 350;
|
|
sphereAngles[YAW] = addSum;
|
|
|
|
AnglesToAxis( sphereAngles, ent.axis );
|
|
|
|
alpha = 255.0 * ((100) / 1750.0) + Q_irand(0, 16);
|
|
if (alpha>255)
|
|
alpha=255;
|
|
|
|
//Slowly pulse the scale up and down
|
|
if (!timeScale)
|
|
{
|
|
addSum = 0;
|
|
}
|
|
else
|
|
{
|
|
addSum = (((cg.time / (int)timeScale) & 255) * (M_PI * 2) / 255)/8;
|
|
|
|
if (addSum >= 0.4)
|
|
{
|
|
addSum = (0.4 - (addSum - 0.4));
|
|
}
|
|
|
|
addSum *= 0.2;
|
|
}
|
|
|
|
xScale += addSum;
|
|
yScale += addSum;
|
|
zScale += addSum;
|
|
|
|
VectorScale( ent.axis[0], xScale, ent.axis[0] );
|
|
VectorScale( ent.axis[1], yScale, ent.axis[1] );
|
|
VectorScale( ent.axis[2], zScale, ent.axis[2] );
|
|
|
|
ent.hModel = trap_R_RegisterModel ( "models/map_objects/mp/sphere.md3" );//cgs.media.explosionModel;
|
|
|
|
if (custShader)
|
|
{
|
|
ent.customShader = custShader;
|
|
}
|
|
else
|
|
{
|
|
ent.customShader = trap_R_RegisterShader( "powerups/ysalimarishell" );//cgs.media.halfShieldShader;
|
|
}
|
|
ent.renderfx = (RF_RGB_TINT|RF_FORCE_ENT_ALPHA);
|
|
if (colors[0] != -1)
|
|
{
|
|
ent.shaderRGBA[0] = colors[0];
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[0] = alpha;
|
|
}
|
|
|
|
if (colors[1] != -1)
|
|
{
|
|
ent.shaderRGBA[1] = colors[1];
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[1] = alpha;
|
|
}
|
|
|
|
if (colors[2] != -1)
|
|
{
|
|
ent.shaderRGBA[2] = colors[2];
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[2] = alpha;
|
|
}
|
|
|
|
if (forceAlpha)
|
|
{
|
|
ent.shaderRGBA[3] = forceAlpha;
|
|
}
|
|
else
|
|
{
|
|
ent.shaderRGBA[3] = 255;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Player
|
|
===============
|
|
*/
|
|
void CG_Player( centity_t *cent ) {
|
|
clientInfo_t *ci;
|
|
refEntity_t legs;
|
|
refEntity_t torso;
|
|
refEntity_t head;
|
|
int clientNum;
|
|
int renderfx;
|
|
qboolean shadow = qfalse;
|
|
float shadowPlane = 0;
|
|
qboolean dead = qfalse;
|
|
vec3_t rootAngles;
|
|
refEntity_t seeker;
|
|
float angle;
|
|
vec3_t angles, dir, elevated, enang, seekorg;
|
|
int iwantout = 0, successchange = 0;
|
|
int team;
|
|
float prefig = 0;
|
|
centity_t *enent;
|
|
mdxaBone_t boltMatrix, lHandMatrix;
|
|
vec3_t efOrg;
|
|
vec3_t tDir;
|
|
int distVelBase;
|
|
int doAlpha = 0;
|
|
int effectTimeLayer = 0;
|
|
qboolean gotLHandMatrix = qfalse;
|
|
|
|
// the client number is stored in clientNum. It can't be derived
|
|
// from the entity number, because a single client may have
|
|
// multiple corpses on the level using the same clientinfo
|
|
clientNum = cent->currentState.clientNum;
|
|
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
|
|
CG_Error( "Bad clientNum on player entity");
|
|
}
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
// it is possible to see corpses from disconnected players that may
|
|
// not have valid clientinfo
|
|
if ( !ci->infoValid ) {
|
|
return;
|
|
}
|
|
|
|
if (!cent->ghoul2)
|
|
{ //not ready yet?
|
|
return;
|
|
}
|
|
|
|
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.teamowner && !cent->isATST)
|
|
{
|
|
cg_entities[cent->currentState.number].currentState.teamowner = cent->currentState.teamowner;
|
|
cg_entities[cent->currentState.number].isATST = cent->isATST;
|
|
|
|
if (CG_RegisterClientModelname(&cgs.clientinfo[cent->currentState.number], cgs.clientinfo[cent->currentState.number].modelName, cgs.clientinfo[cent->currentState.number].skinName,
|
|
cgs.clientinfo[cent->currentState.number].headModelName, cgs.clientinfo[cent->currentState.number].headSkinName, cgs.clientinfo[cent->currentState.number].teamName, cent->currentState.number))
|
|
{
|
|
cent->isATST = 1;
|
|
cg_entities[cent->currentState.number].isATST = cent->isATST;
|
|
return;
|
|
}
|
|
}
|
|
else if (!cent->currentState.teamowner && cent->isATST)
|
|
{
|
|
cg_entities[cent->currentState.number].currentState.teamowner = cent->currentState.teamowner;
|
|
cg_entities[cent->currentState.number].isATST = cent->isATST;
|
|
|
|
if (CG_RegisterClientModelname(&cgs.clientinfo[cent->currentState.number], cgs.clientinfo[cent->currentState.number].modelName, cgs.clientinfo[cent->currentState.number].skinName,
|
|
cgs.clientinfo[cent->currentState.number].headModelName, cgs.clientinfo[cent->currentState.number].headSkinName, cgs.clientinfo[cent->currentState.number].teamName, cent->currentState.number))
|
|
{
|
|
cent->isATST = 0;
|
|
cg_entities[cent->currentState.number].isATST = cent->isATST;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cent->currentState.number < MAX_CLIENTS &&
|
|
cent->currentState.teamowner &&
|
|
!cent->isATST)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 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) {
|
|
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 = g2WeaponInstances[WP_SABER];
|
|
}
|
|
|
|
if (cent->ghoul2 &&
|
|
cent->ghoul2weapon != g2WeaponInstances[cent->currentState.weapon] &&
|
|
!(cent->currentState.eFlags & EF_DEAD) && !cent->torsoBolt && !cent->isATST)
|
|
{
|
|
CG_CopyG2WeaponInstance(cent->currentState.weapon, cent->ghoul2);
|
|
|
|
if (!(cg.snap->ps.pm_flags & PMF_FOLLOW))
|
|
{
|
|
if (cent->weapon == WP_SABER && cent->weapon != cent->currentState.weapon && !cent->currentState.shouldtarget)
|
|
{ //switching away from the saber
|
|
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" ));
|
|
}
|
|
else if (cent->currentState.weapon == WP_SABER && cent->weapon != cent->currentState.weapon)
|
|
{ //switching to the saber
|
|
trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ));
|
|
}
|
|
}
|
|
|
|
cent->weapon = cent->currentState.weapon;
|
|
cent->ghoul2weapon = g2WeaponInstances[cent->currentState.weapon];
|
|
}
|
|
else if ((cent->currentState.eFlags & EF_DEAD) || cent->torsoBolt)
|
|
{
|
|
cent->ghoul2weapon = NULL; //be sure to update after respawning/getting limb regrown
|
|
}
|
|
|
|
|
|
memset (&legs, 0, sizeof(legs));
|
|
|
|
CG_SetGhoul2Info(&legs, cent);
|
|
|
|
VectorSet(legs.modelScale, 1,1,1);
|
|
legs.radius = 64;
|
|
VectorClear(legs.angles);
|
|
|
|
if (cent->isATST)
|
|
{
|
|
legs.radius = 400;
|
|
}
|
|
|
|
// minimal_add:
|
|
|
|
team = cgs.clientinfo[ cent->currentState.clientNum ].team;
|
|
|
|
if (cgs.gametype >= GT_TEAM && 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 (team == TEAM_RED)
|
|
{
|
|
CG_PlayerFloatSprite( cent, cgs.media.teamRedShader);
|
|
}
|
|
else // if (team == TEAM_BLUE)
|
|
{
|
|
CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cent->isATST)
|
|
{
|
|
goto doEssentialOne;
|
|
}
|
|
|
|
if ( (cent->currentState.powerups & (1 << PW_YSALIMARI)) ||
|
|
(cgs.gametype == GT_CTY && ((cent->currentState.powerups & (1 << PW_REDFLAG)) || (cent->currentState.powerups & (1 << PW_BLUEFLAG)))) )
|
|
{
|
|
vec3_t efColors;
|
|
|
|
if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_REDFLAG)))
|
|
{
|
|
efColors[0] = 255;
|
|
efColors[1] = 50;
|
|
efColors[2] = -1;
|
|
}
|
|
else if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_BLUEFLAG)))
|
|
{
|
|
efColors[0] = 50;
|
|
efColors[1] = 50;
|
|
efColors[2] = 255;
|
|
}
|
|
else
|
|
{
|
|
efColors[0] = 255;
|
|
efColors[1] = 255;
|
|
efColors[2] = -1;
|
|
}
|
|
CG_DrawYsalimariSphere(cent, cent->lerpOrigin, efColors);
|
|
}
|
|
|
|
if (cent->currentState.powerups & (1 << PW_FORCE_BOON))
|
|
{
|
|
vec3_t efColors;
|
|
efColors[0] = -1;
|
|
efColors[1] = -1;
|
|
efColors[2] = 255;
|
|
CG_DrawYsalimariSphere(cent, cent->lerpOrigin, efColors);
|
|
}
|
|
|
|
if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_DARK))
|
|
{
|
|
vec3_t efColors;
|
|
efColors[0] = 255;
|
|
efColors[1] = -1;
|
|
efColors[2] = -1;
|
|
CG_DrawYsalimariSphere(cent, cent->lerpOrigin, efColors);
|
|
}
|
|
else if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_LIGHT))
|
|
{
|
|
vec3_t efColors;
|
|
efColors[0] = 255;
|
|
efColors[1] = 255;
|
|
efColors[2] = 255;
|
|
CG_DrawYsalimariSphere(cent, cent->lerpOrigin, efColors);
|
|
}
|
|
|
|
// add the shadow
|
|
shadow = CG_PlayerShadow( cent, &shadowPlane );
|
|
|
|
if ( (cent->currentState.eFlags & EF_SEEKERDRONE) || cent->currentState.genericenemyindex != -1 )
|
|
{
|
|
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 > 1024)
|
|
{
|
|
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)
|
|
{
|
|
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 );
|
|
}
|
|
|
|
doEssentialOne:
|
|
// add a water splash if partially in and out of water
|
|
CG_PlayerSplash( cent );
|
|
|
|
if ( cg_shadows.integer == 3 && 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;
|
|
VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all
|
|
|
|
CG_G2PlayerAngles( cent, legs.axis, rootAngles );
|
|
|
|
//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, cgs.clientinfo[cent->currentState.number].bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
|
|
gotLHandMatrix = qtrue;
|
|
|
|
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) );
|
|
memset( &head, 0, sizeof(head) );
|
|
|
|
if (cent->isATST)
|
|
{
|
|
goto doEssentialTwo;
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
VectorCopy(cent->currentState.pos.trDelta, tDir);
|
|
|
|
distVelBase = SPEED_TRAIL_DISTANCE*(VectorNormalize(tDir)*0.004);
|
|
|
|
if (cent->frame_minus1.ghoul2 && cent->frame_minus1_refreshed)
|
|
{
|
|
cent->frame_minus1.renderfx |= RF_FORCE_ENT_ALPHA;
|
|
cent->frame_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.origin, legs.origin, tDir);
|
|
VectorNormalize(tDir);
|
|
|
|
cent->frame_minus1.origin[0] = legs.origin[0]+tDir[0]*distVelBase;
|
|
cent->frame_minus1.origin[1] = legs.origin[1]+tDir[1]*distVelBase;
|
|
cent->frame_minus1.origin[2] = legs.origin[2]+tDir[2]*distVelBase;
|
|
|
|
trap_R_AddRefEntityToScene(¢->frame_minus1);
|
|
}
|
|
|
|
if (cent->frame_minus2.ghoul2 && cent->frame_minus2_refreshed)
|
|
{
|
|
cent->frame_minus2.renderfx |= RF_FORCE_ENT_ALPHA;
|
|
cent->frame_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.origin, cent->frame_minus1.origin, tDir);
|
|
VectorNormalize(tDir);
|
|
|
|
cent->frame_minus2.origin[0] = cent->frame_minus1.origin[0]+tDir[0]*distVelBase;
|
|
cent->frame_minus2.origin[1] = cent->frame_minus1.origin[1]+tDir[1]*distVelBase;
|
|
cent->frame_minus2.origin[2] = cent->frame_minus1.origin[2]+tDir[2]*distVelBase;
|
|
|
|
trap_R_AddRefEntityToScene(¢->frame_minus2);
|
|
}
|
|
|
|
doEssentialTwo:
|
|
VectorCopy(cent->turAngles, cg_entities[cent->currentState.number].turAngles);
|
|
VectorCopy(legs.origin, cg_entities[cent->currentState.number].lerpOrigin);
|
|
|
|
// 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 );
|
|
|
|
if (cent->currentState.eFlags & EF_DEAD)
|
|
{ //keep track of death anim frame for when we copy off the bodyqueue
|
|
cgs.clientinfo[cent->currentState.number].frame = cent->pe.torso.frame;
|
|
}
|
|
|
|
/* if ( cent->gent->s.number == 0 && cg_thirdPersonAlpha.value < 1.0f )
|
|
{
|
|
ent.renderfx |= RF_ALPHA_FADE;
|
|
ent.shaderRGBA[3] = (unsigned char)(cg_thirdPersonAlpha.value * 255.0f);
|
|
}
|
|
*/
|
|
|
|
if (cent->isATST)
|
|
{
|
|
goto doEssentialThree;
|
|
}
|
|
|
|
//rww - in low framerate situations we may only play the effect once every large duration and depending
|
|
//on the effect this could make it barely visible. So we'll play the effect multiple times per frame
|
|
//judging by the amount of time that's passed since the last frame, thus maintaining the same rate in
|
|
//high framerate situations and compensating by layering the effect multiple times in a single frame in
|
|
//low framerate situations
|
|
if (cent->trailTime < cg.time)
|
|
{
|
|
cent->trailTime = cg.time;
|
|
}
|
|
|
|
//compensate for up to 300ms
|
|
effectTimeLayer = (300 - (cent->trailTime - cg.time));
|
|
|
|
if (effectTimeLayer < 50)
|
|
{ //play at least once
|
|
effectTimeLayer = 50;
|
|
}
|
|
|
|
cent->trailTime = cg.time + 300;
|
|
|
|
//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];
|
|
int effectTimeLayerC = effectTimeLayer;
|
|
sharedBoltInterface_t fxObj;
|
|
|
|
//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, cgs.clientinfo[cent->currentState.number].bolt_head, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
|
|
|
|
trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, efOrg);
|
|
trap_G2API_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];
|
|
|
|
VectorCopy(/*efOrg*/cent->lerpOrigin, fxObj.origin);
|
|
VectorCopy(/*fxAng*/tAng, fxObj.angles);
|
|
VectorCopy(cent->modelScale, fxObj.scale);
|
|
fxObj.ghoul2 = cent->ghoul2;
|
|
fxObj.isValid = 1;
|
|
fxObj.modelNum = 0;
|
|
fxObj.boltNum = cgs.clientinfo[cent->currentState.number].bolt_head;
|
|
fxObj.entNum = cent->currentState.number;
|
|
|
|
while (effectTimeLayerC > 0)
|
|
{
|
|
trap_FX_PlayEntityEffectID(trap_FX_RegisterEffect("force/confusion.efx"), efOrg, axis, cent->boltInfo, cent->currentState.number);
|
|
|
|
//FIXME: Due to the horrible inefficiency involved in the current effect bolt process an effect with as many particles as this won't
|
|
//work too happily. It also doesn't look a lot better due to the lag between origin updates with the effect bolt. If those issues
|
|
//are ever resolved it should be switched over to BoltedEffect.
|
|
//trap_FX_PlayBoltedEffectID(trap_FX_RegisterEffect("force/confusion.efx"), &fxObj);
|
|
effectTimeLayerC -= 50;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cent->currentState.activeForcePass > FORCE_LEVEL_3)
|
|
{
|
|
int effectTimeLayerL = effectTimeLayer;
|
|
|
|
vec3_t axis[3];
|
|
vec3_t tAng, fAng, fxDir;
|
|
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 );
|
|
|
|
//trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
|
|
if (!gotLHandMatrix)
|
|
{
|
|
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].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 );
|
|
|
|
while (effectTimeLayerL > 0)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{//line
|
|
//trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir );
|
|
trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, cent->boltInfo, cent->currentState.number);
|
|
}
|
|
|
|
effectTimeLayerL -= 50;
|
|
}
|
|
|
|
/*
|
|
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 )
|
|
{//doing the electrocuting
|
|
int effectTimeLayerL = effectTimeLayer;
|
|
|
|
vec3_t axis[3];
|
|
vec3_t tAng, fAng, fxDir;
|
|
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, cgs.clientinfo[cent->currentState.number].bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
|
|
if (!gotLHandMatrix)
|
|
{
|
|
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].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 );
|
|
|
|
while (effectTimeLayerL > 0)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{//line
|
|
//trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir );
|
|
trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, cent->boltInfo, cent->currentState.number);
|
|
}
|
|
|
|
effectTimeLayerL -= 50;
|
|
}
|
|
|
|
/*
|
|
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") );
|
|
}
|
|
*/
|
|
}
|
|
|
|
if ( cent->currentState.powerups & (1 << PW_DISINT_4) )
|
|
{
|
|
vec3_t tAng;
|
|
//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, cgs.clientinfo[cent->currentState.number].bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
|
|
if (!gotLHandMatrix)
|
|
{
|
|
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].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];
|
|
|
|
CG_ForcePushBlur( efOrg );
|
|
}
|
|
|
|
if (cent->currentState.weapon == WP_STUN_BATON && (cent->currentState.eFlags & EF_FIRING))
|
|
{ //note: This is a stupid placeholder so that it's at least the same as SP
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, trap_S_RegisterSound("sound/weapons/saber/saberhum") );
|
|
}
|
|
|
|
//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;
|
|
}
|
|
}
|
|
stillDoSaber:
|
|
if (cent->currentState.weapon == WP_SABER && !cent->currentState.shouldtarget)
|
|
{
|
|
if (!cent->currentState.saberInFlight)
|
|
{
|
|
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/saberhum.wav" ) );
|
|
}
|
|
else
|
|
{
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
|
|
trap_S_RegisterSound( "sound/weapons/saber/saberhum.wav" ) );
|
|
}
|
|
}
|
|
|
|
if (iwantout && !cent->currentState.saberInFlight)
|
|
{
|
|
if (cent->currentState.eFlags & EF_DEAD)
|
|
{
|
|
if (cent->ghoul2 && cent->currentState.saberInFlight && trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1))
|
|
{ //special case, kill the saber on a freshly dead player if another source says to.
|
|
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
|
|
}
|
|
}
|
|
//return;
|
|
goto endOfCall;
|
|
}
|
|
|
|
if (cent->currentState.saberInFlight && cent->currentState.saberEntityNum)
|
|
{
|
|
centity_t *saberEnt;
|
|
|
|
saberEnt = &cg_entities[cent->currentState.saberEntityNum];
|
|
|
|
if (/*!cent->bolt4 &&*/ trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1))
|
|
{ //saber is in flight, do not have it as a standard weapon model
|
|
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
|
|
|
|
//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];
|
|
cent->bolt2 = 0;
|
|
|
|
saberEnt->currentState.bolt2 = 123;
|
|
|
|
if (saberEnt->ghoul2)
|
|
{
|
|
// now set up the gun bolt on it
|
|
trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash");
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0);
|
|
|
|
if (saberEnt->ghoul2)
|
|
{
|
|
trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash");
|
|
//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;
|
|
}
|
|
}
|
|
}
|
|
/*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;
|
|
|
|
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
|
|
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 = 999;
|
|
|
|
VectorCopy(saberEnt->lerpAngles, bladeAngles);
|
|
bladeAngles[ROLL] = 0;
|
|
CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, saberEnt->lerpOrigin, bladeAngles, qtrue);
|
|
|
|
//Make the player's hand glow while guiding the saber
|
|
{
|
|
vec3_t tAng;
|
|
float wv;
|
|
addspriteArgStruct_t fxSArgs;
|
|
|
|
VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] );
|
|
|
|
trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cgs.clientinfo[cent->currentState.number].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.yellowSaberGlowShader;
|
|
fxSArgs.flags = 0x08000000;
|
|
trap_FX_AddSprite(&fxSArgs);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
centity_t *saberEnt;
|
|
|
|
saberEnt = &cg_entities[cent->currentState.saberEntityNum];
|
|
|
|
if (/*cent->bolt4 && */!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1))
|
|
{
|
|
trap_G2API_CopySpecificGhoul2Model(g2WeaponInstances[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);
|
|
}
|
|
CG_AddSaberBlade( cent, cent, NULL, 0, 0, legs.origin, rootAngles, qfalse);
|
|
cent->bolt3 = 0;
|
|
cent->bolt2 = 0;
|
|
|
|
//cent->bolt4 = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cent->saberLength = 0;
|
|
}
|
|
|
|
if (cent->currentState.eFlags & EF_DEAD)
|
|
{
|
|
if (cent->ghoul2 && cent->currentState.saberInFlight && trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1))
|
|
{ //special case, kill the saber on a freshly dead player if another source says to.
|
|
trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
|
|
}
|
|
}
|
|
|
|
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] = 50;
|
|
legs.shaderRGBA[1] = 50;
|
|
legs.shaderRGBA[2] = 50;
|
|
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;
|
|
}
|
|
|
|
legs.shaderRGBA[0] = 0;
|
|
legs.shaderRGBA[1] = 0;
|
|
legs.shaderRGBA[2] = 255 - subLen/4;
|
|
|
|
if (legs.shaderRGBA[2] < 1) legs.shaderRGBA[2] = 1;
|
|
|
|
legs.renderfx &= ~RF_RGB_TINT;
|
|
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
|
|
legs.customShader = cgs.media.forceSightBubble;
|
|
|
|
trap_R_AddRefEntityToScene( &legs );
|
|
|
|
legs.customShader = 0;
|
|
|
|
legs.shaderRGBA[0] = 255 - subLen/8;
|
|
legs.shaderRGBA[1] = 255 - subLen/8;
|
|
legs.shaderRGBA[2] = subLen/8;
|
|
|
|
if (legs.shaderRGBA[2] < 1)
|
|
{
|
|
legs.shaderRGBA[2] = 1;
|
|
}
|
|
if (legs.shaderRGBA[2] > 255)
|
|
{
|
|
legs.shaderRGBA[2] = 255;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
doEssentialThree:
|
|
if (cent->currentState.eFlags & EF_DISINTEGRATION)
|
|
{
|
|
vec3_t tempAng, hitLoc;
|
|
float tempLength;
|
|
|
|
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;
|
|
}
|
|
|
|
trap_G2API_SetBoneAnim(legs.ghoul2, 0, "model_root", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1);
|
|
trap_G2API_SetBoneAnim(legs.ghoul2, 0, "upper_lumbar", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1);
|
|
trap_G2API_SetBoneAnim(legs.ghoul2, 0, "Motion", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1);
|
|
|
|
VectorCopy(cent->currentState.origin2, hitLoc);
|
|
|
|
VectorSubtract( hitLoc, legs.origin, legs.oldorigin );
|
|
|
|
tempLength = VectorNormalize( legs.oldorigin );
|
|
vectoangles( legs.oldorigin, tempAng );
|
|
tempAng[YAW] -= cent->lerpAngles[YAW];
|
|
AngleVectors( tempAng, legs.oldorigin, NULL, NULL );
|
|
VectorScale( legs.oldorigin, tempLength, legs.oldorigin );
|
|
legs.endTime = cent->dustTrailTime;
|
|
|
|
//FIXME: Sorting is all wrong here, it can't decide if it should render this part or the outer body first
|
|
/*
|
|
legs.renderfx |= RF_DISINTEGRATE2;
|
|
legs.customShader = cgs.media.disruptorShader;
|
|
trap_R_AddRefEntityToScene( &legs );
|
|
*/
|
|
|
|
legs.renderfx &= ~(RF_DISINTEGRATE2);
|
|
legs.renderfx |= (RF_DISINTEGRATE1);
|
|
|
|
legs.customShader = 0;
|
|
trap_R_AddRefEntityToScene( &legs );
|
|
|
|
goto endOfCall;
|
|
}
|
|
else
|
|
{
|
|
cent->dustTrailTime = 0;
|
|
cent->miscTime = 0;
|
|
}
|
|
|
|
trap_R_AddRefEntityToScene(&legs);
|
|
|
|
if (cent->isATST)
|
|
{
|
|
//return;
|
|
goto endOfCall;
|
|
}
|
|
|
|
cent->frame_minus2 = cent->frame_minus1;
|
|
if (cent->frame_minus1_refreshed)
|
|
{
|
|
cent->frame_minus2_refreshed = 1;
|
|
}
|
|
cent->frame_minus1 = legs;
|
|
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)
|
|
{
|
|
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.ghoul2 && trap_G2_HaveWeGhoul2Models(cent->frame_hold.ghoul2) &&
|
|
cent->frame_hold.ghoul2 != cent->ghoul2)
|
|
{
|
|
trap_G2API_CleanGhoul2Models(&(cent->frame_hold.ghoul2));
|
|
}
|
|
cent->frame_hold = legs;
|
|
cent->frame_hold_refreshed = 1;
|
|
cent->frame_hold.ghoul2 = NULL;
|
|
|
|
trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->frame_hold.ghoul2);
|
|
|
|
//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.ghoul2, 0, "model_root", cent->frame_hold.frame, cent->frame_hold.frame, 0, 1.0f, cg.time, cent->frame_hold.frame, -1);
|
|
}
|
|
|
|
cent->frame_hold.renderfx |= RF_FORCE_ENT_ALPHA;
|
|
cent->frame_hold.shaderRGBA[3] = (cent->frame_hold_time - cg.time);
|
|
if (cent->frame_hold.shaderRGBA[3] > 254)
|
|
{
|
|
cent->frame_hold.shaderRGBA[3] = 254;
|
|
}
|
|
if (cent->frame_hold.shaderRGBA[3] < 1)
|
|
{
|
|
cent->frame_hold.shaderRGBA[3] = 1;
|
|
}
|
|
|
|
trap_R_AddRefEntityToScene(¢->frame_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 (cent->currentState.isJediMaster && cg.snap->ps.clientNum != 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.renderfx |= RF_DEPTHHACK;
|
|
legs.customShader = cgs.media.forceSightBubble;
|
|
|
|
trap_R_AddRefEntityToScene( &legs );
|
|
|
|
legs.renderfx &= ~RF_DEPTHHACK;
|
|
}
|
|
|
|
if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number && cg_auraShell.integer)
|
|
{
|
|
if (cgs.gametype >= GT_TEAM)
|
|
{ // A team game
|
|
switch(cgs.clientinfo[ cent->currentState.clientNum ].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_DEPTHHACK;
|
|
|
|
if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2)
|
|
{ //only level 2+ can see players through walls
|
|
legs.renderfx &= ~RF_DEPTHHACK;
|
|
}
|
|
}
|
|
|
|
legs.renderfx &= ~RF_RGB_TINT;
|
|
legs.renderfx &= ~RF_FORCE_ENT_ALPHA;
|
|
legs.customShader = cgs.media.forceSightBubble;
|
|
|
|
trap_R_AddRefEntityToScene( &legs );
|
|
}
|
|
|
|
// Electricity
|
|
//------------------------------------------------
|
|
if ( cent->currentState.emplacedOwner > cg.time )
|
|
{
|
|
int dif = cent->currentState.emplacedOwner - cg.time;
|
|
|
|
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, trap_S_RegisterSound( "sound/effects/energy_crackle.wav" ) );
|
|
}
|
|
}
|
|
|
|
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 );
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
//=====================================================================
|
|
|
|
/*
|
|
===============
|
|
CG_ResetPlayerEntity
|
|
|
|
A player just came into view or teleported, so reset all animation info
|
|
===============
|
|
*/
|
|
void CG_ResetPlayerEntity( centity_t *cent )
|
|
{
|
|
cent->errorTime = -99999; // guarantee no error decay added
|
|
cent->extrapolated = qfalse;
|
|
|
|
CG_ClearLerpFrame( cent, &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim, qfalse);
|
|
CG_ClearLerpFrame( cent, &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim, qtrue);
|
|
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
|
|
BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
|
|
|
|
VectorCopy( cent->lerpOrigin, cent->rawOrigin );
|
|
VectorCopy( cent->lerpAngles, cent->rawAngles );
|
|
|
|
memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) );
|
|
cent->pe.legs.yawAngle = cent->rawAngles[YAW];
|
|
cent->pe.legs.yawing = qfalse;
|
|
cent->pe.legs.pitchAngle = 0;
|
|
cent->pe.legs.pitching = qfalse;
|
|
|
|
memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) );
|
|
cent->pe.torso.yawAngle = cent->rawAngles[YAW];
|
|
cent->pe.torso.yawing = qfalse;
|
|
cent->pe.torso.pitchAngle = cent->rawAngles[PITCH];
|
|
cent->pe.torso.pitching = qfalse;
|
|
|
|
if ((cent->ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model))
|
|
{
|
|
trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model, ¢->ghoul2);
|
|
CG_CopyG2WeaponInstance(cent->currentState.weapon, cgs.clientinfo[cent->currentState.clientNum].ghoul2Model);
|
|
cent->weapon = cent->currentState.weapon;
|
|
}
|
|
|
|
if ( cg_debugPosition.integer ) {
|
|
CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
|
|
}
|
|
}
|
|
|