// // cg_players.c -- handle the media and animation for player entities #define cg_players_c //RPG-X: J2J - Added to help solve LNK2005 errors (special case for cg_players.c) #include "cg_local.h" #include "cg_screenfx.h" #include "fx_local.h" //#include "cg_anims.h" //RPG-X: J2J - Added for animation string table. const char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { "*death1.wav", "*death2.wav", "*death3.wav", "*jump1.wav", "*pain25.wav", "*pain50.wav", "*pain75.wav", "*pain100.wav", "*falling1.wav", "*gasp.wav", "*drown.wav", "*fall1.wav", "*taunt1.wav", "*taunt2.wav", "*taunt3.wav", "*taunt4.wav", "*taunt5.wav" }; stringID_table_t BoltonTable[BOLTON_MAX + 1] = { { ENUM2STRING(BOLTON_HEAD) }, { ENUM2STRING(BOLTON_TORSO) }, { ENUM2STRING(BOLTON_LEGS) }, { NULL, -1 } }; int timeParam; //int beamTimeParam; //RPG-X : TiM - Beaming int entNum; extern char* BG_RegisterRace( const char *name ); /* ================ 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; } /* ============================================================================= ANIM SOUND CONFIG LOADING AND PLAYING ============================================================================= */ /* void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, const vec3_t org, int entNum ) play any keyframed sounds - only when start a new frame This func is called once for legs and once for torso */ //void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, int entNum, qboolean metal ) void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, int eNum, int surfType) { int i; int holdSnd = -1; qboolean playSound = qfalse; /*if ( entNum == cg.predictedPlayerState.clientNum && !cg.renderingThirdPerson ) {//player in first person view does not play any keyframed sounds return; }*/ // Check for anim sound for (i=0;i irandom(0, 99) ) { playSound = qtrue; } break; } } // Play sound if (holdSnd != -1 && playSound) { if (holdSnd != 0) // 0 = default sound, ie file was missing { trap_S_StartSound( NULL, eNum, CHAN_BODY, holdSnd ); //CHAN_AUTO } } } void ParseAnimationSndBlock(const char *filename, animsounds_t *animSounds, animation_t *animations, int *i,char **text_p) { char *token; char soundString[MAX_QPATH]; int lowestVal, highestVal; int animNum, num, n; // get past starting bracket while(1) { token = COM_Parse( text_p ); if ( !Q_stricmp( token, "{" ) ) { break; } } animSounds += *i; // read information for each frame while ( 1 ) { // Get base frame of sequence token = COM_Parse( text_p ); if ( !token || !token[0]) { break; } if ( !Q_stricmp( token, "}" ) ) // At end of block { break; } //Compare to same table as animations used // so we don't have to use actual numbers for animation first frames, // just need offsets. //This way when animation numbers change, this table won't have to be updated, // at least not much. animNum = GetIDForString(animTable, token); if(animNum == -1) {//Unrecognized ANIM ENUM name, or we're skipping this line, keep going till you get a good one Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animSound file %s\n", token, filename ); continue; } if ( animations[animNum].numFrames == 0 ) {//we don't use this anim Com_Printf(S_COLOR_YELLOW"WARNING: %s animsounds.cfg: anim %s not used by this model\n", filename, token); // Get offset to frame within sequence token = COM_Parse( text_p ); //get soundstring token = COM_Parse( text_p ); //get lowest value token = COM_Parse( text_p ); //get highest value token = COM_Parse( text_p ); //get probability token = COM_Parse( text_p ); continue; } animSounds->keyFrame = animations[animNum].firstFrame; // Get offset to frame within sequence token = COM_Parse( text_p ); if ( !token ) { break; } animSounds->keyFrame += atoi( token ); //get soundstring token = COM_Parse( text_p ); if ( !token ) { break; } strcpy(soundString, token); //get lowest value token = COM_Parse( text_p ); if ( !token ) {//WARNING! BAD TABLE! break; } lowestVal = atoi( token ); //get highest value token = COM_Parse( text_p ); if ( !token ) {//WARNING! BAD TABLE! break; } highestVal = atoi( token ); //Now precache all the sounds //NOTE: If we can be assured sequential handles, we can store sound indices // instead of strings, unfortunately, if these sounds were previously // registered, we cannot be guaranteed sequential indices. Thus an array if(lowestVal && highestVal) { for ( n = lowestVal, num = 0; n <= highestVal && num < MAX_RANDOM_ANIMSOUNDS; n++, num++ ) { animSounds->soundIndex[num] = trap_S_RegisterSound( va( soundString, n ) ); } animSounds->numRandomAnimSounds = num - 1; } else { animSounds->soundIndex[0] = trap_S_RegisterSound( va( soundString ) ); #ifndef FINAL_BUILD if ( !animSounds->soundIndex[0] ) {//couldn't register it - file not found Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (animsound.cfg %s)!\n", soundString, filename ); } #endif animSounds->numRandomAnimSounds = 0; } //get probability token = COM_Parse( text_p ); if ( !token ) {//WARNING! BAD TABLE! break; } animSounds->probability = atoi( token ); ++animSounds; ++*i; } } /* ====================== CG_ParseAnimationSndFile Read a configuration file containing animation sounds models/players/munro/animsounds.cfg, etc This file's presence is not required ====================== */ #ifdef QVM static int CG_ParseAnimationSndFile( const char *filename, int animFileIndex ) { char *text_p; int len; char *token; char text[20000]; fileHandle_t f; int i, j, upper_i, lower_i; animsounds_t *lowerAnimSounds; animsounds_t *upperAnimSounds; animation_t *animations; /*if ( knownAnimFileSets[animFileIndex].soundsCached ) {//already cached this one return; }*/ for ( i = 0; i < cg_numSndAnims; i++ ) { if ( !Q_stricmp( filename, cg_animsSndList[i].animSndFileRoute ) ) { return i; } } //Mark this anim set so that we know we tried to load he sounds, don't care if the load failed //knownAnimFileSets[animFileIndex].soundsCached = qtrue; animations = cg_animsList[animFileIndex].animations; lowerAnimSounds = cg_animsSndList[cg_numSndAnims].lowerAnimSounds; upperAnimSounds = cg_animsSndList[cg_numSndAnims].upperAnimSounds; //initialize anim sound array for(i = 0; i < MAX_ANIM_SOUNDS; i++) { upperAnimSounds[i].numRandomAnimSounds = 0; lowerAnimSounds[i].numRandomAnimSounds = 0; for(j = 0; j < MAX_RANDOM_ANIMSOUNDS; j++) { upperAnimSounds[i].soundIndex[j] = -1; lowerAnimSounds[i].soundIndex[j] = -1; } } // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) {//no file return -1; } if ( len >= sizeof( text ) - 1 ) { CG_Printf( "File %s too long\n", filename ); return -1; } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = text; upper_i =0; lower_i =0; // read information for batches of sounds (UPPER or LOWER) while ( 1 ) { // Get base frame of sequence token = COM_Parse( &text_p ); if ( !token || !token[0] ) { break; } if ( !Q_stricmp(token,"UPPERSOUNDS") ) // A batch of upper sounds { ParseAnimationSndBlock( filename, upperAnimSounds, animations, &upper_i, &text_p ); } else if ( !Q_stricmp(token,"LOWERSOUNDS") ) // A batch of lower sounds { ParseAnimationSndBlock( filename, lowerAnimSounds, animations, &lower_i, &text_p ); } } i = cg_numSndAnims; cg_numSndAnims++; return i; } #else static int CG_ParseAnimationSndFile( const char *filename, int animFileIndex ) { char *text_p; int len; char *token; char *text; fileHandle_t f; int i, j, upper_i, lower_i; animsounds_t *lowerAnimSounds; animsounds_t *upperAnimSounds; animation_t *animations; text = (char *)malloc(sizeof(char)*20000); if(!text) { Com_Error(ERR_FATAL, "CG_ParseAnimationSndFile: couldn't alloce cate %u byte\n", sizeof(char)*20000); return 0; } /*if ( knownAnimFileSets[animFileIndex].soundsCached ) {//already cached this one return; }*/ for ( i = 0; i < cg_numSndAnims; i++ ) { if ( !Q_stricmp( filename, cg_animsSndList[i].animSndFileRoute ) ) { free(text); return i; } } //Mark this anim set so that we know we tried to load he sounds, don't care if the load failed //knownAnimFileSets[animFileIndex].soundsCached = qtrue; animations = cg_animsList[animFileIndex].animations; lowerAnimSounds = cg_animsSndList[cg_numSndAnims].lowerAnimSounds; upperAnimSounds = cg_animsSndList[cg_numSndAnims].upperAnimSounds; //initialize anim sound array for(i = 0; i < MAX_ANIM_SOUNDS; i++) { upperAnimSounds[i].numRandomAnimSounds = 0; lowerAnimSounds[i].numRandomAnimSounds = 0; for(j = 0; j < MAX_RANDOM_ANIMSOUNDS; j++) { upperAnimSounds[i].soundIndex[j] = -1; lowerAnimSounds[i].soundIndex[j] = -1; } } // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) {//no file free(text); return -1; } if ( len >= sizeof( text ) - 1 ) { CG_Printf( "File %s too long\n", filename ); free(text); return -1; } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = text; upper_i =0; lower_i =0; // read information for batches of sounds (UPPER or LOWER) while ( 1 ) { // Get base frame of sequence token = COM_Parse( &text_p ); if ( !token || !token[0] ) { break; } if ( !Q_stricmp(token,"UPPERSOUNDS") ) // A batch of upper sounds { ParseAnimationSndBlock( filename, upperAnimSounds, animations, &upper_i, &text_p ); } else if ( !Q_stricmp(token,"LOWERSOUNDS") ) // A batch of lower sounds { ParseAnimationSndBlock( filename, lowerAnimSounds, animations, &lower_i, &text_p ); } } i = cg_numSndAnims; cg_numSndAnims++; free(text); return i; } #endif /* ============================================================================= MODEL SCRIPT LOADING ============================================================================= */ /* ====================== CG_ParseAnimationFile Read a configuration file containing animation coutns and rates models/players_rpgx/munro/animation.cfg, etc TiM: Small modification. Based on the nuber of animations parsed, this will return an index to the cell in the global animation array where the relevant animation data is kept. Based on both the JKA concept and the EF method of caching assets, this is far more efficient since it means that if two people use models with the same body models using the same animation data, the anim data will ony need be loaded once :) A lot more efficient considering how many freakin more animations we introduced with this model system lol. ====================== */ // //static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { #ifdef QVM static int CG_ParseAnimationFile( const char* filename/*, clientInfo_t *ci*/ ) { char *text_p, *prev; int len; int i; char *token; float fps; int skip; char text[20000]; fileHandle_t f; animation_t *animations; //CG_Printf( "Anim is %s\n", filename ); if ( cg_numAnims > 0 ) { for ( i = 0; i <= cg_numAnims; i++ ) { if ( !Q_stricmpn( cg_animsList[i].animFileRoute, filename, (int)strlen( filename ) ) ) { //We found a matching anim set //Com_Printf( S_COLOR_RED "Using index: %i\n", i ); return i; } } } // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { CG_Printf( S_COLOR_RED "File %s not found\n", filename ); return -1; //qfalse } if ( len >= sizeof( text ) - 1 ) { CG_Printf( S_COLOR_RED "File %s too long\n", filename ); return -1; //qfalse } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); //animations = ci->animations; animations = cg_animsList[cg_numAnims].animations; //copy the file name to the gloabl anims array. It doesn't matter //if it returns false, since the same cell will be flushed on the next call then. memset( cg_animsList[cg_numAnims].animFileRoute, 0, MAX_QPATH ); Q_strncpyz( cg_animsList[cg_numAnims].animFileRoute, filename, MAX_QPATH ); //flush the anims memset( animations, 0, sizeof( animations ) ); // parse the text text_p = text; skip = 0; // quite the compiler warning /* ci->footsteps = FOOTSTEP_NORMAL; VectorClear( ci->headOffset ); ci->gender = GENDER_MALE; Q_strncpyz(ci->soundPath, ci->modelName, sizeof(ci->soundPath));*/ // read optional parameters while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); if ( !token ) { break; } /*if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { ci->footsteps = FOOTSTEP_NORMAL; } else if ( !Q_stricmp( token, "borg" ) ) { ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "reaver" ) ) { ci->footsteps = FOOTSTEP_REAVER; } else if ( !Q_stricmp( token, "species" ) ) { ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "warbot" ) ) { ci->footsteps = FOOTSTEP_WARBOT; } else if ( !Q_stricmp( token, "boot" ) ) { ci->footsteps = FOOTSTEP_BOOT; } else if ( !Q_stricmp( token, "flesh" ) ) { // Old Q3 defaults, for compatibility. -PJL ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "mech" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "energy" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else { CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); } continue; } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } ci->headOffset[i] = atof( token ); } continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( token[0] == 'f' || token[0] == 'F' ) { ci->gender = GENDER_FEMALE; } else if ( token[0] == 'n' || token[0] == 'N' ) { ci->gender = GENDER_NEUTER; } else { ci->gender = GENDER_MALE; } continue; } else if ( !Q_stricmp( token, "soundpath" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } Q_strncpyz(ci->soundPath,token,sizeof (ci->soundPath) ); continue; } */ // if it is a number, start parsing animations if ( token[0] >= '0' && token[0] <= '9' ) { text_p = prev; // unget the token break; } Com_Printf( "unknown token '%s' is %s\n", token, filename ); } // read information for each frame for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].firstFrame = atoi( token ); // leg only frames are adjusted to not count the upper body only frames if ( i == LEGS_KNEEL1 ) { //LEGS_WALKCR skip = animations[LEGS_KNEEL1].firstFrame - animations[TORSO_ACTIVATEMEDKIT1].firstFrame; //TORSO_GESTURE } if ( i >= LEGS_KNEEL1 ) { animations[i].firstFrame -= skip; } token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].numFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].loopFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } fps = atof( token ); if ( fps == 0 ) { fps = 1; } animations[i].frameLerp = 1000 / fps; animations[i].initialLerp = 1000 / fps; } if ( i != MAX_ANIMATIONS ) { CG_Printf( S_COLOR_RED "Error parsing animation file: %s", filename ); //return qfalse; return -1; } //CG_Printf( S_COLOR_RED "Cached File: %s\n", cgs.animsList[cgs.numAnims].animFileRoute ); //return ++cg_numAnims; i = cg_numAnims; if ( cg_numAnims < MAX_CLIENTS ) { cg_numAnims++; //offset for the next time :) } return i; //return qtrue; } #else static int CG_ParseAnimationFile( const char* filename/*, clientInfo_t *ci*/ ) { char *text_p, *prev; int len; int i; char *token; float fps; int skip; char *text; fileHandle_t f; animation_t *animations; //CG_Printf( "Anim is %s\n", filename ); text = (char *)malloc(sizeof(char)*20000); if(!text) { CG_Printf("CG_ParseAnimationFile: couldn't allocate %u bytes\n", sizeof(char)*20000); return -1; } if ( cg_numAnims > 0 ) { for ( i = 0; i <= cg_numAnims; i++ ) { if ( !Q_stricmpn( cg_animsList[i].animFileRoute, filename, (int)strlen( filename ) ) ) { //We found a matching anim set //Com_Printf( S_COLOR_RED "Using index: %i\n", i ); free(text); return i; } } } // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { CG_Printf( S_COLOR_RED "File %s not found\n", filename ); free(text); return -1; //qfalse } if ( len >= sizeof( text ) - 1 ) { CG_Printf( S_COLOR_RED "File %s too long\n", filename ); free(text); return -1; //qfalse } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); //animations = ci->animations; animations = cg_animsList[cg_numAnims].animations; //copy the file name to the gloabl anims array. It doesn't matter //if it returns false, since the same cell will be flushed on the next call then. memset( cg_animsList[cg_numAnims].animFileRoute, 0, MAX_QPATH ); Q_strncpyz( cg_animsList[cg_numAnims].animFileRoute, filename, MAX_QPATH ); //flush the anims memset( animations, 0, sizeof( animations ) ); // parse the text text_p = text; skip = 0; // quite the compiler warning /* ci->footsteps = FOOTSTEP_NORMAL; VectorClear( ci->headOffset ); ci->gender = GENDER_MALE; Q_strncpyz(ci->soundPath, ci->modelName, sizeof(ci->soundPath));*/ // read optional parameters while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); if ( !token ) { break; } /*if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { ci->footsteps = FOOTSTEP_NORMAL; } else if ( !Q_stricmp( token, "borg" ) ) { ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "reaver" ) ) { ci->footsteps = FOOTSTEP_REAVER; } else if ( !Q_stricmp( token, "species" ) ) { ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "warbot" ) ) { ci->footsteps = FOOTSTEP_WARBOT; } else if ( !Q_stricmp( token, "boot" ) ) { ci->footsteps = FOOTSTEP_BOOT; } else if ( !Q_stricmp( token, "flesh" ) ) { // Old Q3 defaults, for compatibility. -PJL ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "mech" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "energy" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else { CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); } continue; } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } ci->headOffset[i] = atof( token ); } continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( token[0] == 'f' || token[0] == 'F' ) { ci->gender = GENDER_FEMALE; } else if ( token[0] == 'n' || token[0] == 'N' ) { ci->gender = GENDER_NEUTER; } else { ci->gender = GENDER_MALE; } continue; } else if ( !Q_stricmp( token, "soundpath" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } Q_strncpyz(ci->soundPath,token,sizeof (ci->soundPath) ); continue; } */ // if it is a number, start parsing animations if ( token[0] >= '0' && token[0] <= '9' ) { text_p = prev; // unget the token break; } Com_Printf( "unknown token '%s' is %s\n", token, filename ); } // read information for each frame for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].firstFrame = atoi( token ); // leg only frames are adjusted to not count the upper body only frames if ( i == LEGS_KNEEL1 ) { //LEGS_WALKCR skip = animations[LEGS_KNEEL1].firstFrame - animations[TORSO_ACTIVATEMEDKIT1].firstFrame; //TORSO_GESTURE } if ( i >= LEGS_KNEEL1 ) { animations[i].firstFrame -= skip; } token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].numFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].loopFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } fps = atof( token ); if ( fps == 0 ) { fps = 1; } animations[i].frameLerp = 1000 / fps; animations[i].initialLerp = 1000 / fps; } if ( i != MAX_ANIMATIONS ) { CG_Printf( S_COLOR_RED "Error parsing animation file: %s", filename ); //return qfalse; free(text); return -1; } //CG_Printf( S_COLOR_RED "Cached File: %s\n", cgs.animsList[cgs.numAnims].animFileRoute ); //return ++cg_numAnims; i = cg_numAnims; if ( cg_numAnims < MAX_CLIENTS ) { cg_numAnims++; //offset for the next time :) } free(text); return i; //return qtrue; } #endif /* ====================== CG_InitModelData by TiM Initialize default values in case the crazy modder left out some of the keys. In most cases, the fields will just be left blank. No point in using extra resources if they weren't specified. ====================== */ static void CG_InitModelData( clientInfo_t *ci ) { ci->holsterModel = 0; ci->hasRanks = qfalse; //initialize all model + skin data as 0, so it can be told if they don't get //values assigned in the script parser, in which case we exit. ci->headModel = 0; ci->torsoModel = 0; ci->legsModel = 0; ci->headSkin = 0; ci->headSkinBlink = 0; //doesn't matter if left 0; won't end the parser ci->headSkinFrown = 0; ci->headSkinFrownBlink = 0; ci->torsoSkin = 0; ci->legsSkin = 0; //doesn't matter if left 0 ci->headBlinkTime.minSeconds = 0; ci->headBlinkTime.maxSeconds = 0; ci->nextTalkTime = 0; ci->currentTalkSkin = 0; ci->headSkinTalk[0] = 0; ci->headSkinTalk[1] = 0; ci->headSkinTalk[2] = 0; ci->headSkinTalk[3] = 0; //animation.cfg former inits ci->footsteps = FOOTSTEP_NORMAL; VectorClear( ci->headOffset ); ci->gender = GENDER_MALE; Q_strncpyz(ci->soundPath, ci->charName, sizeof(ci->soundPath)); //set animIndex to -1. if still -1 at the end, we return false, coz we gotz no anims ci->animIndex = -1; ci->animSndIndex = -1; memset( &ci->boltonTags, 0, sizeof(ci->boltonTags)); ci->isHazardModel = qfalse; } /* ===================== CG_ParseSkinSetDataFile by TiM Parses a separate.skinset file to get the skin data for this model. ====================== */ static qboolean CG_ParseSkinSetDataFile( clientInfo_t *ci, const char *skinSetFrame, const char *charName, const char *skinName ) { char* skinStar; char skinSetName[MAX_QPATH]; char skinSetRoute[MAX_QPATH]; char* token; char* textPtr; char buffer[5000]; int len; fileHandle_t f; int n, i; if ( ( skinStar = strstr( skinSetFrame, "*" ) ) == NULL ) { CG_Printf( S_COLOR_RED "ERROR: No '*' specified in model skin set!\n" ); return qfalse; } else { //star is at front if ( skinStar == skinSetFrame ) { skinStar++; Com_sprintf( skinSetName, sizeof( skinSetName ), "%s%s", skinName, skinStar ); } //star is at end else if ((int)(skinStar - skinSetFrame)+1 == (int)strlen(skinSetFrame) ) { Q_strncpyz( skinSetName, skinSetFrame, strlen( skinSetFrame ) ); Q_strcat( skinSetName, sizeof( skinSetName ), skinName ); } else { CG_Printf( "ERROR: The '*' in %s must be on either the start or end, not the middle.\n", skinSetFrame ); return qfalse; } } //CG_Printf( S_COLOR_RED "DEBUG: skinSetName = %s \n", skinSetName ); Com_sprintf( skinSetRoute, sizeof( skinSetRoute ), "models/players_rpgx/%s/%s.skinset", charName, skinSetName ); len = trap_FS_FOpenFile( skinSetRoute, &f, FS_READ ); if ( len <= 0 ) { CG_Printf( S_COLOR_RED "ERROR: Could not open file: %s\n", skinSetRoute ); return qfalse; } if ( len > sizeof( buffer) - 1 ) { CG_Printf( S_COLOR_RED "ERROR: Imported file is too big for buffer: %s. Len is %i\n", skinSetRoute, len ); return qfalse; } trap_FS_Read( buffer, len, f ); trap_FS_FCloseFile( f ); if ( !buffer[0] ) { CG_Printf( S_COLOR_RED "ERROR: Could not import data from %s\n", skinSetRoute ); return qfalse; } buffer[len] = '\0'; textPtr = buffer; token = COM_Parse( &textPtr ); if ( Q_stricmp( token, "{" ) ) { CG_Printf( S_COLOR_RED "ERROR: Skinset %s did not start with a '{'\n", skinSetRoute ); return qfalse; } else { while ( 1 ) { //while we don't hit the closing brace token = COM_Parse( &textPtr ); //parse if ( !token[0] ) { //error check break; } //head skin when blinking //must be before headskin, or the damn thing will think the two are the same :P if ( !Q_stricmp( token, "headSkinBlink" ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } if ( !cg_noBlinkingHeads.integer ) { ci->headSkinBlink = trap_R_RegisterSkin( token ); //We'll alert them, but not cancel the loop if ( !ci->headSkinBlink ) Com_Printf( S_COLOR_RED "WARNING: Couldn't load headSkinBlink: %s\n", token); } continue; } //head blink time else if ( !Q_stricmpn( token, "headBlinkTime", 13 ) ) { //Done this way so we know we got two valid args b4 proceeding if ( COM_ParseInt( &textPtr, &n ) ) { //first arg SkipRestOfLine( &textPtr ); continue; } if ( COM_ParseInt( &textPtr, &i ) ) { //2nd arg SkipRestOfLine( &textPtr ); continue; } //Bug: if the stupid n00b of a modder made //the minimum time larger than the max time >.< if ( n > i ) { Com_Printf( S_COLOR_RED "ERROR: Minimum blink time was larger than maximum blink time.\n" ); continue; } if ( !cg_noBlinkingHeads.integer ) { ci->headBlinkTime.minSeconds = n; ci->headBlinkTime.maxSeconds = i; } continue; } else if ( !Q_stricmpn( token, "headSkinFrownBlink", 18 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } if ( !cg_noFrowningHeads.integer ) { ci->headSkinFrownBlink = trap_R_RegisterSkin( token ); if ( !ci->headSkinFrownBlink ) Com_Printf( S_COLOR_RED "WARNING: Was unable to load frown blink skin: %s\n", token ); } continue; } else if ( !Q_stricmpn( token, "headSkinFrown", 13 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } //Only cache if we want to if ( !cg_noFrowningHeads.integer ) { ci->headSkinFrown = trap_R_RegisterSkin( token ); } if ( !cg_noFrowningHeads.integer && !ci->headSkinFrown ) { Com_Printf( S_COLOR_RED "WARNING: Couldn't load frowning skin: %s\n", token ); } continue; } else if ( !Q_stricmpn( token, "torsoSkin", 9 ) ) { if (COM_ParseString( &textPtr, &token ) ) { continue; } ci->torsoSkin = trap_R_RegisterSkin( token ); if (!ci->torsoSkin ) { Com_Printf( S_COLOR_RED "ERROR: Couldn't load torsoSkin: %s\n", token); } continue; } else if ( !Q_stricmpn( token, "legsSkin", 8 ) ) { if (COM_ParseString( &textPtr, &token ) ) { continue; } ci->legsSkin = trap_R_RegisterSkin( token ); if (!ci->legsSkin ) { Com_Printf( S_COLOR_RED "ERROR: Couldn't load legsSkin: %s\n", token); } continue; } else if ( !Q_stricmpn( token, "headSkinTalk", 12 ) && !cg_noTalkingHeads.integer ) { token = COM_Parse( &textPtr ); //parse if ( !token[0] ) { //error check break; } // if we found no {, then scrub the whole thing if ( Q_stricmpn( token, "{", 1 ) ) { continue; } else { i = 0; while ( 1 ) { token = COM_Parse( &textPtr ); //parse if ( !token[0] ) { //error check break; } ci->headSkinTalk[i] = trap_R_RegisterSkin( token ); if ( !ci->headSkinTalk[i] ) { Com_Printf( S_COLOR_RED "ERROR: Unable to parse headSkinTalk file: %s\n", token ); break; } i++; //Com_Printf("Registered Skin: %i\n", i); if ( !Q_stricmpn( token, "}", 1) ) { break; } if ( i >= MAX_TALK_SKINS ) { break; } } } continue; } //head skin else if ( !Q_stricmpn( token, "headSkin", 8 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } ci->headSkin = trap_R_RegisterSkin( token ); if ( !ci->headSkin ) { Com_Printf( S_COLOR_RED "ERROR: Couldn't load headSkin: %s\n", token ); return qfalse; } continue; } if ( !Q_stricmpn( token, "}", 1) ) { break; } } } return qtrue; } /* ====================== CG_ParseModelDataFile by TiM Reads in the .model file needed to put together a character model. ====================== */ #ifdef QVM static qboolean CG_ParseModelDataFile( clientInfo_t *ci, const char *charName, const char *modelName, const char *skinName ) { fileHandle_t file; int file_len; char charText[20000]; char *textPtr, *prevValue; char fileName[MAX_QPATH]; //char animPath[MAX_QPATH]; int i, n; char *token; char legsFileRoute[MAX_QPATH]; char animSndFileRoute[MAX_QPATH]; qboolean skinSetFound=qfalse; //size_t strLen; //create the file route Com_sprintf( fileName, sizeof(fileName), "models/players_rpgx/%s/%s.model", charName, modelName); //Okay... gotta get the hang of ANSI C text parsing >.< //first... I guess load the file file_len = trap_FS_FOpenFile( fileName, &file, FS_READ ); //Error handle //if length was 0, ie file not found or was empty if (file_len <= 0 ) { return qfalse; } //Another error... if text is WAY bigger than our available buffer O_O if ( file_len >= sizeof( charText ) - 1 ) { Com_Printf( S_COLOR_RED "Model Data File %s too long... WAY too long\n", fileName ); return qfalse; } //initialize the buffer memset( charText, 0, sizeof( charText ) ); //read data into char array //i guess we use a char array so we can actually specify size/width. trap_FS_Read( charText, file_len, file ); //I guess this is needed to mark the EOF. charText[file_len] = 0; //Free memory. Close Files trap_FS_FCloseFile( file ); //default values if needed CG_InitModelData( ci ); //Used to just clear any previous parse temp data COM_BeginParseSession(); //transfer our data from a char array to a char ptr. //needed for the parsing func methinks textPtr = charText; token = COM_Parse( &textPtr ); //COM_Parse seems to work by splitting up each line of text by the spaces, //and then removes that chunk from the original //Okay, we should have the beginning variable first... which should be a '{' //from the looks of this, I think we have to do this after //every parse call. O_O if ( !token[0] ) { Com_Printf( S_COLOR_RED "No data found in model data buffer!\n"); return qfalse; } if ( Q_stricmp(token, "{" ) ) { Com_Printf(S_COLOR_RED "Missing { in %s\n", fileName); return qfalse; } while ( 1 ) { prevValue = textPtr; //set a backup token = COM_Parse( &textPtr ); if (!token[0] || !token ) { //we've hit the end of the file. w00t! exit! break; } //if we randomly find a brace in here (ie a sub-struct that may have no header) //just skip it. :P if ( !Q_stricmpn( token, "{", 1 ) ) { SkipBracedSection ( &textPtr ); } if ( !Q_stricmpn( token, "animsConfig", 11 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->animIndex = CG_ParseAnimationFile( token ); //no valid anim file found. Don't give up hope though. //We have a backup resort at the end if need be. :) if ( ci->animIndex == -1 ) { Com_Printf( S_COLOR_RED "WARNING: Was unable to load file %s.\n", token ); } continue; } //anim sounds config file else if ( !Q_stricmpn( token, "animSoundsConfig", 16 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } //check to see if we have a valid animlist we can sync these //sounds to. if not, we'll put the file route asside, and //try again at the end. if ( ci->animIndex >= 0 ) { ci->animSndIndex = CG_ParseAnimationSndFile( token, ci->animIndex ); if ( ci->animSndIndex == -1 ) { Com_Printf( S_COLOR_RED "WARNING: Unable to load file: %s\n", token ); } } else { Q_strncpyz( animSndFileRoute, token, sizeof( animSndFileRoute ) ); } continue; } //character's legs model else if ( !Q_stricmpn( token, "legsModel", 9 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->legsModel = trap_R_RegisterModel( token ); if (!ci->legsModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load legs model: %s\n", token); return qfalse; } //if loaded no anims yet, copy the legs route to this variable, //and we'll try again at the end of the function //if ( ci->animIndex == -1 ) { Q_strncpyz( legsFileRoute, token, sizeof( legsFileRoute ) ); //} Actually. just copy it regardless. Just in case continue; } //character's torso model else if ( !Q_stricmpn( token, "torsoModel", 10 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->torsoModel = trap_R_RegisterModel( token ); //Com_Printf("Torsomodel passed as %s, %i\n", token, (int)ci->torsoModel); if (!ci->torsoModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load torso model: %s\n", token); return qfalse; } continue; } //character's headmodel else if ( !Q_stricmpn( token, "headModel", 9 ) ) { //return true = no extra text found on this line - bad! O_O! if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->headModel = trap_R_RegisterModel( token ); if (!ci->headModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load head model: %s\n", token); return qfalse; } continue; } //holster model (basically just a null md3 with 2 tags: one for phaser, other for tric) else if ( !Q_stricmpn( token, "holsterModel", 12 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->holsterModel = trap_R_RegisterModel( token ); //You'd hope like hell this will never happen. :P if (!ci->holsterModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load holster model: %s\n", token); return qfalse; } continue; } // Custom bolton models... oi O_o else if ( !Q_stricmpn( token, "boltonModels", 12 ) ) { //needed coz '{' could also be on next line token = COM_Parse( &textPtr ); if ( !token[0] ) { //if that was it break; } else { //else, if next character is '{' if ( !Q_stricmpn( token, "{", 1 ) ) { token = COM_Parse( &textPtr ); if ( !token[0] ) { break; } //loop till we hit the end of the brackets i = 0; while ( Q_stricmp( token, "}" ) ) { if ( !Q_stricmpn( token, "BOLTON_", 7 ) ) { ci->boltonTags[i].modelBase = GetIDForString( BoltonTable, token ); if( COM_ParseString( &textPtr, &token ) ) { continue; } if (!Q_stricmpn( token, "tag_", 4 ) ) { Q_strncpyz(ci->boltonTags[i].tagName, token, sizeof (ci->boltonTags[i].tagName) ); if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->boltonTags[i].tagModel = trap_R_RegisterModel( token ); if (!ci->boltonTags[i].tagModel) { Com_Printf( S_COLOR_RED "WARNING: Unable to load bolton model: %s\n", token); } i++; if (i > MAX_BOLTONS -1) { break; } } } //Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[ci->numBoltOns].modelBase, ci->boltonTags[ci->numBoltOns].tagName, ci->boltonTags[ci->numBoltOns].tagModel ); token = COM_Parse( &textPtr ); if ( !token[0] ) { break; } } } } } //whether char is allowed to wear ranks else if ( !Q_stricmpn( token, "hasRanks", 8 ) ) { if (COM_ParseInt(&textPtr, &n ) ) { continue; } if ( n > 0 ) ci->hasRanks = qtrue; else ci->hasRanks = qfalse; continue; } //player footsteps. //FIXME: Is it possible to make these things dynamic, so we can //put in our own footstep sounds? /*else if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &textPtr ); if ( !token ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { ci->footsteps = FOOTSTEP_NORMAL; } else if ( !Q_stricmp( token, "borg" ) ) { ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "reaver" ) ) { ci->footsteps = FOOTSTEP_REAVER; } else if ( !Q_stricmp( token, "species" ) ) { ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "warbot" ) ) { ci->footsteps = FOOTSTEP_WARBOT; } else if ( !Q_stricmp( token, "boot" ) ) { ci->footsteps = FOOTSTEP_BOOT; } else if ( !Q_stricmp( token, "flesh" ) ) { // Old Q3 defaults, for compatibility. -PJL ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "mech" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "energy" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else { CG_Printf( "Bad footsteps parm in %s: %s\n", fileName, token ); } continue; } */ //offset for player head in the scoreboard or whatever else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &textPtr ); if ( !token ) { break; } ci->headOffset[i] = atof( token ); } continue; } //what gender the character is else if ( !Q_stricmpn( token, "sex", 3 ) ) { if (COM_ParseString( &textPtr, &token ) ) { continue; } if ( token[0] == 'f' || token[0] == 'F' ) { ci->gender = GENDER_FEMALE; } else if ( token[0] == 'n' || token[0] == 'N' ) { ci->gender = GENDER_NEUTER; } else { ci->gender = GENDER_MALE; } continue; } //file path to model sound files else if ( !Q_stricmpn( token, "soundPath", 9 ) ) { if (COM_ParseString( &textPtr, &token ) ){ continue; } Q_strncpyz( ci->soundPath, token, sizeof(ci->soundPath) ); continue; } //TiM - The skinset is defined else if ( !Q_stricmpn( token, "skinSet", 7 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } if ( CG_ParseSkinSetDataFile( ci, token, charName, skinName ) ) { skinSetFound = qtrue; } continue; } } //if any of the models or skins were left blank, then output false. Coz we need them. :P if (!ci->headModel || !ci->torsoModel || !ci->legsModel ) { Com_Printf( S_COLOR_RED "One or more necessary model files weren't loaded from %s\n", fileName ); return qfalse; } if ( !skinSetFound ) { if ( !CG_ParseSkinSetDataFile( ci, va("%s_*", modelName, skinName ), charName, skinName ) ) { CG_Printf( S_COLOR_RED "ERROR: Tried loading default skin set, however it failed.\n"); } } if (!ci->headSkin || !ci->torsoSkin || !ci->legsSkin ) { Com_Printf( S_COLOR_RED "One or more necessary skin files weren't loaded from %s\n", fileName ); return qfalse; } //if modder specified no animations file route, or they did, and it sucked (ie -1 ), //Then try looking for one in the same directory as the lower.mdr file //k... the goal of this is to take a string like //models/players_rpgx/crewman_male/lower.mdr //and turn it into //models/players_rpgx/crewman_male/animation.cfg if ( ci->animIndex == -1 && strlen( legsFileRoute ) > 0 ) { //get length of file route i = (int)strlen(legsFileRoute); while( 1 ) { //if we looped all the way to the end.... ie BAD if (i <= 0) { //we obviously have no animation directory :( Com_Printf(S_COLOR_RED "ERROR: Was unable to calculate location of animation.cfg for %s\n", fileName); return qfalse; } //if this is the first '/' we come across from going from the end to the start if (legsFileRoute[i] == '/' ) { //copy i bytes of data from token to animpath (effectively giving us the route, with no file) Q_strncpyz(legsFileRoute, legsFileRoute, (i = i + 2 )); //+2 for the null char these things auto assign at the end... i think break; //won't work without it anyway :P } i--; } //add animation.cfg to the end of the string Q_strcat(legsFileRoute, sizeof(legsFileRoute), "animation.cfg"); //Com_Printf( S_COLOR_RED "WARNING: Failed to load animation file specified in model config, attempting to load %s\n", legsFileRoute ); //parse it ^_^ ci->animIndex = CG_ParseAnimationFile( legsFileRoute ); if ( ci->animIndex < 0 ) { Com_Printf( "Tried loading anim data from location %s, however nothing was valid.\n", legsFileRoute ); return qfalse; } } else { if ( !legsFileRoute[0] ) { Com_Printf( S_COLOR_RED "Couldn't load/locate any player animation data for player: %s.\n", charName ); return qfalse; } } //We'll check again if we can load a sound config file after everything else if ( ci->animSndIndex == -1 && animSndFileRoute[0] ) { ci->animSndIndex = CG_ParseAnimationSndFile( animSndFileRoute, ci->animIndex ); if ( ci->animSndIndex == -1 ) { Com_Printf( S_COLOR_RED "ERROR: Unable to load sound config file: %s.\n", animSndFileRoute ); } } ci->animsFlushed = qfalse; //TiM: Cheap hack - let us specifically check for hazard models if ( !Q_stricmp( modelName, "hazard" ) ) ci->isHazardModel = qtrue; //holy fudgenuggets. after all that checking, we actually made it to the end and have a valid freaking //model! OWNED! return qtrue; } #else static qboolean CG_ParseModelDataFile( clientInfo_t *ci, const char *charName, const char *modelName, const char *skinName ) { fileHandle_t file; int file_len; char *charText; char *textPtr, *prevValue; char fileName[MAX_QPATH]; //char animPath[MAX_QPATH]; int i, n; char *token; char legsFileRoute[MAX_QPATH]; char animSndFileRoute[MAX_QPATH]; qboolean skinSetFound=qfalse; //size_t strLen; charText = (char *)malloc(sizeof(char)*20000); if(!charText) { CG_Printf("CG_ParseModelDataFile: couldn't allocate %u byte\n", sizeof(char)*20000); return qfalse; } //create the file route Com_sprintf( fileName, sizeof(fileName), "models/players_rpgx/%s/%s.model", charName, modelName); //Okay... gotta get the hang of ANSI C text parsing >.< //first... I guess load the file file_len = trap_FS_FOpenFile( fileName, &file, FS_READ ); //Error handle //if length was 0, ie file not found or was empty if (file_len <= 0 ) { free(charText); return qfalse; } //Another error... if text is WAY bigger than our available buffer O_O if ( file_len >= sizeof( charText ) - 1 ) { Com_Printf( S_COLOR_RED "Model Data File %s too long... WAY too long\n", fileName ); free(charText); return qfalse; } //initialize the buffer memset( charText, 0, sizeof( charText ) ); //read data into char array //i guess we use a char array so we can actually specify size/width. trap_FS_Read( charText, file_len, file ); //I guess this is needed to mark the EOF. charText[file_len] = 0; //Free memory. Close Files trap_FS_FCloseFile( file ); //default values if needed CG_InitModelData( ci ); //Used to just clear any previous parse temp data COM_BeginParseSession(); //transfer our data from a char array to a char ptr. //needed for the parsing func methinks textPtr = charText; token = COM_Parse( &textPtr ); //COM_Parse seems to work by splitting up each line of text by the spaces, //and then removes that chunk from the original //Okay, we should have the beginning variable first... which should be a '{' //from the looks of this, I think we have to do this after //every parse call. O_O if ( !token[0] ) { Com_Printf( S_COLOR_RED "No data found in model data buffer!\n"); free(charText); return qfalse; } if ( Q_stricmp(token, "{" ) ) { Com_Printf(S_COLOR_RED "Missing { in %s\n", fileName); free(charText); return qfalse; } while ( 1 ) { prevValue = textPtr; //set a backup token = COM_Parse( &textPtr ); if (!token[0] || !token ) { //we've hit the end of the file. w00t! exit! break; } //if we randomly find a brace in here (ie a sub-struct that may have no header) //just skip it. :P if ( !Q_stricmpn( token, "{", 1 ) ) { SkipBracedSection ( &textPtr ); } if ( !Q_stricmpn( token, "animsConfig", 11 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->animIndex = CG_ParseAnimationFile( token ); //no valid anim file found. Don't give up hope though. //We have a backup resort at the end if need be. :) if ( ci->animIndex == -1 ) { Com_Printf( S_COLOR_RED "WARNING: Was unable to load file %s.\n", token ); } continue; } //anim sounds config file else if ( !Q_stricmpn( token, "animSoundsConfig", 16 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } //check to see if we have a valid animlist we can sync these //sounds to. if not, we'll put the file route asside, and //try again at the end. if ( ci->animIndex >= 0 ) { ci->animSndIndex = CG_ParseAnimationSndFile( token, ci->animIndex ); if ( ci->animSndIndex == -1 ) { Com_Printf( S_COLOR_RED "WARNING: Unable to load file: %s\n", token ); } } else { Q_strncpyz( animSndFileRoute, token, sizeof( animSndFileRoute ) ); } continue; } //character's legs model else if ( !Q_stricmpn( token, "legsModel", 9 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->legsModel = trap_R_RegisterModel( token ); if (!ci->legsModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load legs model: %s\n", token); free(charText); return qfalse; } //if loaded no anims yet, copy the legs route to this variable, //and we'll try again at the end of the function //if ( ci->animIndex == -1 ) { Q_strncpyz( legsFileRoute, token, sizeof( legsFileRoute ) ); //} Actually. just copy it regardless. Just in case continue; } //character's torso model else if ( !Q_stricmpn( token, "torsoModel", 10 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->torsoModel = trap_R_RegisterModel( token ); //Com_Printf("Torsomodel passed as %s, %i\n", token, (int)ci->torsoModel); if (!ci->torsoModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load torso model: %s\n", token); return qfalse; } continue; } //character's headmodel else if ( !Q_stricmpn( token, "headModel", 9 ) ) { //return true = no extra text found on this line - bad! O_O! if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->headModel = trap_R_RegisterModel( token ); if (!ci->headModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load head model: %s\n", token); free(charText); return qfalse; } continue; } //holster model (basically just a null md3 with 2 tags: one for phaser, other for tric) else if ( !Q_stricmpn( token, "holsterModel", 12 ) ) { if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->holsterModel = trap_R_RegisterModel( token ); //You'd hope like hell this will never happen. :P if (!ci->holsterModel) { Com_Printf( S_COLOR_RED "ERROR: Unable to load holster model: %s\n", token); free(charText); return qfalse; } continue; } // Custom bolton models... oi O_o else if ( !Q_stricmpn( token, "boltonModels", 12 ) ) { //needed coz '{' could also be on next line token = COM_Parse( &textPtr ); if ( !token[0] ) { //if that was it break; } else { //else, if next character is '{' if ( !Q_stricmpn( token, "{", 1 ) ) { token = COM_Parse( &textPtr ); if ( !token[0] ) { break; } //loop till we hit the end of the brackets i = 0; while ( Q_stricmp( token, "}" ) ) { if ( !Q_stricmpn( token, "BOLTON_", 7 ) ) { ci->boltonTags[i].modelBase = GetIDForString( BoltonTable, token ); if( COM_ParseString( &textPtr, &token ) ) { continue; } if (!Q_stricmpn( token, "tag_", 4 ) ) { Q_strncpyz(ci->boltonTags[i].tagName, token, sizeof (ci->boltonTags[i].tagName) ); if( COM_ParseString( &textPtr, &token ) ) { continue; } ci->boltonTags[i].tagModel = trap_R_RegisterModel( token ); if (!ci->boltonTags[i].tagModel) { Com_Printf( S_COLOR_RED "WARNING: Unable to load bolton model: %s\n", token); } i++; if (i > MAX_BOLTONS -1) { break; } } } //Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[ci->numBoltOns].modelBase, ci->boltonTags[ci->numBoltOns].tagName, ci->boltonTags[ci->numBoltOns].tagModel ); token = COM_Parse( &textPtr ); if ( !token[0] ) { break; } } } } } //whether char is allowed to wear ranks else if ( !Q_stricmpn( token, "hasRanks", 8 ) ) { if (COM_ParseInt(&textPtr, &n ) ) { continue; } if ( n > 0 ) ci->hasRanks = qtrue; else ci->hasRanks = qfalse; continue; } //player footsteps. //FIXME: Is it possible to make these things dynamic, so we can //put in our own footstep sounds? /*else if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &textPtr ); if ( !token ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { ci->footsteps = FOOTSTEP_NORMAL; } else if ( !Q_stricmp( token, "borg" ) ) { ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "reaver" ) ) { ci->footsteps = FOOTSTEP_REAVER; } else if ( !Q_stricmp( token, "species" ) ) { ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "warbot" ) ) { ci->footsteps = FOOTSTEP_WARBOT; } else if ( !Q_stricmp( token, "boot" ) ) { ci->footsteps = FOOTSTEP_BOOT; } else if ( !Q_stricmp( token, "flesh" ) ) { // Old Q3 defaults, for compatibility. -PJL ci->footsteps = FOOTSTEP_SPECIES; } else if ( !Q_stricmp( token, "mech" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else if ( !Q_stricmp( token, "energy" ) ) { // Ditto ci->footsteps = FOOTSTEP_BORG; } else { CG_Printf( "Bad footsteps parm in %s: %s\n", fileName, token ); } continue; } */ //offset for player head in the scoreboard or whatever else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &textPtr ); if ( !token ) { break; } ci->headOffset[i] = atof( token ); } continue; } //what gender the character is else if ( !Q_stricmpn( token, "sex", 3 ) ) { if (COM_ParseString( &textPtr, &token ) ) { continue; } if ( token[0] == 'f' || token[0] == 'F' ) { ci->gender = GENDER_FEMALE; } else if ( token[0] == 'n' || token[0] == 'N' ) { ci->gender = GENDER_NEUTER; } else { ci->gender = GENDER_MALE; } continue; } //file path to model sound files else if ( !Q_stricmpn( token, "soundPath", 9 ) ) { if (COM_ParseString( &textPtr, &token ) ){ continue; } Q_strncpyz( ci->soundPath, token, sizeof(ci->soundPath) ); continue; } //TiM - The skinset is defined else if ( !Q_stricmpn( token, "skinSet", 7 ) ) { if ( COM_ParseString( &textPtr, &token ) ) { continue; } if ( CG_ParseSkinSetDataFile( ci, token, charName, skinName ) ) { skinSetFound = qtrue; } continue; } } //if any of the models or skins were left blank, then output false. Coz we need them. :P if (!ci->headModel || !ci->torsoModel || !ci->legsModel ) { Com_Printf( S_COLOR_RED "One or more necessary model files weren't loaded from %s\n", fileName ); free(charText); return qfalse; } if ( !skinSetFound ) { if ( !CG_ParseSkinSetDataFile( ci, va("%s_*", modelName, skinName ), charName, skinName ) ) { CG_Printf( S_COLOR_RED "ERROR: Tried loading default skin set, however it failed.\n"); } } if (!ci->headSkin || !ci->torsoSkin || !ci->legsSkin ) { Com_Printf( S_COLOR_RED "One or more necessary skin files weren't loaded from %s\n", fileName ); free(charText); return qfalse; } //if modder specified no animations file route, or they did, and it sucked (ie -1 ), //Then try looking for one in the same directory as the lower.mdr file //k... the goal of this is to take a string like //models/players_rpgx/crewman_male/lower.mdr //and turn it into //models/players_rpgx/crewman_male/animation.cfg if ( ci->animIndex == -1 && strlen( legsFileRoute ) > 0 ) { //get length of file route i = (int)strlen(legsFileRoute); while( 1 ) { //if we looped all the way to the end.... ie BAD if (i <= 0) { //we obviously have no animation directory :( Com_Printf(S_COLOR_RED "ERROR: Was unable to calculate location of animation.cfg for %s\n", fileName); free(charText); return qfalse; } //if this is the first '/' we come across from going from the end to the start if (legsFileRoute[i] == '/' ) { //copy i bytes of data from token to animpath (effectively giving us the route, with no file) Q_strncpyz(legsFileRoute, legsFileRoute, (i = i + 2 )); //+2 for the null char these things auto assign at the end... i think break; //won't work without it anyway :P } i--; } //add animation.cfg to the end of the string Q_strcat(legsFileRoute, sizeof(legsFileRoute), "animation.cfg"); //Com_Printf( S_COLOR_RED "WARNING: Failed to load animation file specified in model config, attempting to load %s\n", legsFileRoute ); //parse it ^_^ ci->animIndex = CG_ParseAnimationFile( legsFileRoute ); if ( ci->animIndex < 0 ) { Com_Printf( "Tried loading anim data from location %s, however nothing was valid.\n", legsFileRoute ); free(charText); return qfalse; } } else { if ( !legsFileRoute[0] ) { Com_Printf( S_COLOR_RED "Couldn't load/locate any player animation data for player: %s.\n", charName ); free(charText); return qfalse; } } //We'll check again if we can load a sound config file after everything else if ( ci->animSndIndex == -1 && animSndFileRoute[0] ) { ci->animSndIndex = CG_ParseAnimationSndFile( animSndFileRoute, ci->animIndex ); if ( ci->animSndIndex == -1 ) { Com_Printf( S_COLOR_RED "ERROR: Unable to load sound config file: %s.\n", animSndFileRoute ); } } ci->animsFlushed = qfalse; //TiM: Cheap hack - let us specifically check for hazard models if ( !Q_stricmp( modelName, "hazard" ) ) ci->isHazardModel = qtrue; //holy fudgenuggets. after all that checking, we actually made it to the end and have a valid freaking //model! OWNED! free(charText); return qtrue; } #endif /* ============================================================================= CLIENT INFO ============================================================================= */ /* //This function has been rpg-x'edŽ! (by J2J and fixed by RedTechie) static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { char *text_p; int len; int i; char *token; // char aniname[255]; float fps; int skip; char text[20000]; // char text2[20000]; fileHandle_t f; animation_t *animations; //Com_Printf(S_COLOR_RED"MAX_ANIMATIONS = %i\n", MAX_ANIMATIONS); //not needed animations = ci->animations; // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( text ) - 1 ) { CG_Printf( "File %s too long\n", filename ); return qfalse; } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = text; skip = 0; // quite the compiler warning //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 for(i = 0; i < MAX_ANIMATIONS; i++) { animations[i].firstFrame = 0; animations[i].numFrames = 0; animations[i].loopFrames = -1; animations[i].frameLerp = 100; //Before redtechie change 0 animations[i].initialLerp = 100;//Before redtechie change 0 } while(1) { token = COM_Parse( &text_p ); if ( !token || !token[0] ) { break; } i = GetIDForString(animTable, token); if(i == -1) { Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename); continue; } token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].firstFrame = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].numFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } animations[i].loopFrames = atoi( token ); token = COM_Parse( &text_p ); if ( !token ) { break; } fps = atof( token ); //Com_Printf(S_COLOR_RED"INFO: fps = %s - %f\n", token, fps); //debug only if ( fps == 0 ) { fps = 1;//Don't allow divide by zero error } if ( fps < 0 ) {//backwards animations[i].frameLerp = floor(1000.0f / fps); } else { animations[i].frameLerp = ceil(1000.0f / fps); } animations[i].initialLerp = ceil(1000.0f / fabs(fps)); } return qtrue; } */ /* ========================== \CG_RegisterClientSkin ========================== */ /* ========================== CG_RegisterClientSkin ========================== */ /*static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName ) { char filename[MAX_QPATH]; Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower_%s.skin", modelName, skinName ); ci->legsSkin = trap_R_RegisterSkin( filename ); Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper_%s.skin", modelName, skinName ); ci->torsoSkin = trap_R_RegisterSkin( filename ); Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head_%s.skin", modelName, skinName ); ci->headSkin = trap_R_RegisterSkin( filename ); //Com_sprintf( filename, sizeof( filename ), "models/players2/%s/groups.cfg", modelName); //strcpy(ci->race, BG_RegisterRace( filename )); if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { return qfalse; } return qtrue; }*/ /* ========================== CG_RegisterClientModelname ========================== */ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *charName, const char *modelName, const char *skinName ) { char filename[MAX_QPATH]; // load cmodels before models so filecache works /*Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.mdr", modelName ); ci->legsModel = trap_R_RegisterModel( filename ); if ( !ci->legsModel ) { Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.md3", modelName ); ci->legsModel = trap_R_RegisterModel( filename ); if ( !ci->legsModel ) { Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename ); return qfalse; } } Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper.mdr", modelName ); ci->torsoModel = trap_R_RegisterModel( filename ); if ( !ci->torsoModel ) { Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper.md3", modelName ); ci->torsoModel = trap_R_RegisterModel( filename ); if ( !ci->torsoModel ) { Com_Printf( "Failed to load model file %s\n", filename ); return qfalse; } } Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head.md3", modelName ); ci->headModel = trap_R_RegisterModel( filename ); if ( !ci->headModel ) { Com_Printf( "Failed to load model file %s\n", filename ); return qfalse; } // if any skins failed to load, return failure if ( !CG_RegisterClientSkin( ci, modelName, skinName ) ) { Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); return qfalse; } // load the animations Com_sprintf( filename, sizeof( filename ), "models/players2/%s/animation.cfg", modelName ); if ( !CG_ParseAnimationFile( filename, ci ) ) {\ Com_Printf( "Failed to load animation file %s\n", filename ); return qfalse; }*/ if ( !CG_ParseModelDataFile( ci, charName, modelName, skinName) ) { //Com_Printf( S_COLOR_RED "ERROR: Failed to parse .model file for character: %s/%s/%s\n", charName, modelName, skinName ); return qfalse; } /*Com_sprintf( filename, sizeof( filename ), "models/players2/%s/icon_%s.jpg", modelName, skinName ); ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); if ( !ci->modelIcon ) { Com_Printf( "Failed to load icon file: %s\n", filename ); return qfalse; }*/ Com_sprintf( filename, sizeof( filename ), "models/players_rpgx/%s/model_icon.jpg", charName ); ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); if ( !ci->modelIcon ) { Com_Printf( S_COLOR_RED "ERROR: Failed to load icon file: %s\n", filename ); //return qfalse; } return qtrue; } /* ==================== CG_ColorFromString ==================== */ /*static void CG_ColorFromString( const char *v, vec3_t color ) { int val; VectorClear( color ); val = atoi( v ); if ( val < 1 || val > 7 ) { VectorSet( color, 1, 1, 1 ); return; } if ( val & 1 ) { color[2] = 1.0f; } if ( val & 2 ) { color[1] = 1.0f; } if ( val & 4 ) { color[0] = 1.0f; } }*/ /* =================== CG_LoadClientInfo Load it now, taking the disk hits. This will usually be deferred to a safe time =================== */ static void CG_LoadClientInfo( clientInfo_t *ci , int clientNum) { const char *dir, *fallback; int i; const char *s; char temp_string[200]; //if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) ) { //Com_Printf("charName = %s, modelName = %s, skinName = %s\n", ci->charName, ci->modelName, ci->skinName); if ( !CG_RegisterClientModelname( ci, ci->charName, ci->modelName, ci->skinName ) ) { if ( cg_buildScript.integer ) { CG_Error( "CG_RegisterClientModelname( %s/%s/%s ) failed", ci->charName, ci->modelName, ci->skinName ); } Com_Printf( S_COLOR_RED "ERROR: Failed to parse .model file for character: %s/%s/%s\n", ci->charName, ci->modelName, ci->skinName ); if ( !CG_RegisterClientModelname( ci, ci->charName, DEFAULT_MODEL, ci->skinName ) ) { if ( !CG_RegisterClientModelname( ci, cg_defaultChar.string, DEFAULT_MODEL, ci->skinName ) ) { // fall back if ( cgs.gametype >= GT_TEAM ) { // keep skin name if ( !CG_RegisterClientModelname( ci, DEFAULT_CHAR, DEFAULT_MODEL, ci->skinName ) ) { CG_Error( "DEFAULT_CHAR / model /skin ( %s/%s/%s ) failed to register", DEFAULT_CHAR, DEFAULT_MODEL, ci->skinName ); } } else { if ( !CG_RegisterClientModelname( ci, cg_defaultChar.string, DEFAULT_MODEL, DEFAULT_SKIN ) ) { if ( !CG_RegisterClientModelname( ci, DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ) ) { CG_Error( "DEFAULT_CHAR (%s) failed to register", DEFAULT_CHAR ); } } } } } } // sounds dir = ci->soundPath; fallback = (ci->gender==GENDER_FEMALE)?"hm_female":"hm_male"; for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { s = cg_customSoundNames[i]; if ( !s ) { break; } ci->sounds[i] = trap_S_RegisterSound( va("sound/voice/%s/misc/%s", dir, s + 1) ); if ( !ci->sounds[i] ) { ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1) ); } } ci->deferred = qfalse; Com_sprintf(temp_string, sizeof(temp_string), "%s/%s/%s", ci->charName, ci->modelName, ci->skinName); updateSkin(clientNum, temp_string); // reset any existing players and bodies, because they might be in bad // frames for this new model for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { if ( cg_entities[i].currentState.clientNum == clientNum && cg_entities[i].currentState.eType == ET_PLAYER ) { CG_ResetPlayerEntity( &cg_entities[i] ); } } } // we need to check here to see if the clientinfo model variable is the same as the one that is in the // clientinfo block. This is because it is possible for the server to change skins on us when we hit a CTF // teamplay game where groups are defined. // most of the time this will not hit void updateSkin(int clientNum, char *new_model) { char model_string[200]; // create string to be checked against trap_Cvar_VariableStringBuffer("model", model_string, sizeof(model_string) ); if (Q_stricmp(new_model, model_string) && cg.validPPS && (clientNum == cg.predictedPlayerState.clientNum)) { trap_Cvar_Set_No_Modify ("model",new_model); } } /* ====================== CG_CopyClientInfoModel ====================== */ static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { //int i; VectorCopy( from->headOffset, to->headOffset ); to->footsteps = from->footsteps; to->gender = from->gender; to->numTaunts = from->numTaunts; 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->animIndex = from->animIndex; to->animSndIndex = from->animSndIndex; to->hasRanks = from->hasRanks; //Blinking if ( from->headSkinBlink ) { to->headSkinBlink = from->headSkinBlink; if ( from->headBlinkTime.maxSeconds > 0 ) { //memcpy( to->headBlinkTime, from->headBlinkTime, sizeof( to->headBlinkTime ) ); to->headBlinkTime = from->headBlinkTime; } } //Frowning/Blink Frowning if ( from->headSkinFrown ) { to->headSkinFrown = from->headSkinFrown; if ( from->headSkinFrownBlink ) to->headSkinFrownBlink = from->headSkinFrownBlink; } //Copy over bolton info /*if ( from->boltonTags[0].tagModel && from->boltonTags[0].tagName ) { //if there actually is bolton data... for (i = 0; i < MAX_BOLTONS; i++ ) { //loop thru all of them if ( from->boltonTags[i].tagModel && from->boltonTags[i].tagName ) { //only work on ones that actually have data. to->boltonTags[i].modelBase = from->boltonTags[i].modelBase; to->boltonTags[i].tagModel = from->boltonTags[i].tagModel; Q_strncpyz( to->boltonTags[i].tagName, from->boltonTags[i].tagName, sizeof (to->boltonTags[i].tagName) );*/ memcpy( to->boltonTags, from->boltonTags, sizeof(to->boltonTags) ); // } // } // } //Talking skin data /*if ( from->headSkinTalk[0] ) { for (i = 0; i < MAX_TALK_SKINS; i++ ) { if ( from->headSkinTalk[i] ) {*/ memcpy( to->headSkinTalk, from->headSkinTalk, sizeof( to->headSkinTalk ) ); /* } } }*/ to->holsterModel = from->holsterModel; Q_strncpyz( to->soundPath, from->soundPath, sizeof (to->soundPath) ); //memcpy( to->animations, from->animations, sizeof( to->animations ) ); //TiM : New animation method :) to->animIndex = from->animIndex; memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); to->isHazardModel = from->isHazardModel; } /* ====================== 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->charName, match->charName) && !Q_stricmp( ci->modelName, match->modelName ) && !Q_stricmp( ci->skinName, match->skinName ) ) { // 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 clientNum ) { int i; clientInfo_t *match; // if we are in teamplay, only grab a model if the skin is correct if ( cgs.gametype >= GT_TEAM ) { // this is ONLY for optimization - it's exactly the same effect as CG_LoadClientInfo for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid ) { continue; } if ( Q_stricmp( ci->skinName, match->skinName ) ) { 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, clientNum ); 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 ,clientNum); } /* ====================== CG_NewClientInfo ====================== */ void CG_NewClientInfo( int clientNum ) { clientInfo_t *ci; clientInfo_t newInfo; const char *configstring; const char *v; char *model = NULL; char *skin = NULL; size_t len; //int i; ci = &cgs.clientinfo[clientNum]; configstring = CG_ConfigString( clientNum + CS_PLAYERS ); if ( !configstring[0] ) { memset( ci, 0, sizeof( *ci ) ); return; // player just left } // build into a temp buffer so the defer checks can use // the old value memset( &newInfo, 0, sizeof( newInfo ) ); // isolate the player's name v = Info_ValueForKey(configstring, "n"); Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); // colors //v = Info_ValueForKey( configstring, "c1" ); //CG_ColorFromString( v, newInfo.color ); // 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 ); // playerclass v = Info_ValueForKey( configstring, "p" ); newInfo.pClass = atoi( v ); // model v = Info_ValueForKey( configstring, "model" ); if ( cg_forceModel.integer ) { // forcemodel makes everyone use a single model // to prevent load hitches char charStr[MAX_QPATH]; char *model; char *skin; size_t len; trap_Cvar_VariableStringBuffer( "model", charStr, sizeof( charStr ) ); if ( ( model = strchr( charStr, '/' ) ) == NULL) { model = "main"; skin = "default"; } else { *model = 0; //*model++ = 0; len = strlen(model); //if there was a slash, but no model afterwards if ( !model || !model[1] ) { model = "main"; } if ( ( skin = strchr( model, '/' ) ) == NULL ) { skin = "default"; } else { *skin = 0; //*skin++ = 0; if ( !skin || !skin[1] ) { skin = "default"; } Com_sprintf( model, len - strlen(skin), model); } } Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); Q_strncpyz( newInfo.charName, charStr, sizeof( newInfo.charName ) ); // Q_strncpyz( newInfo.modelName, DEFAULT_MODEL, sizeof( newInfo.modelName ) ); // Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); if ( cgs.gametype >= GT_TEAM ) { // keep skin name skin = strchr( v, '/' ); if ( model ) { Q_strncpyz( newInfo.skinName, skin + 1, sizeof( newInfo.skinName ) ); } } } else { //Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); //Okay! Here we go! We gotta take a string like kulhane/admiral/teal //divide it, and put it into three different vars, accounting for user n00biness //(ie mistakes and stuff) along the way. //step 1, take the first bit of the string and put it in the charName var. if ( ( model = strchr( v, '/') ) == NULL ) { //if there's no slash Q_strncpyz( newInfo.charName, v, sizeof( newInfo.charName ) ); //just set it } else { //otherwise, isolate the first bit, and copy that len = strlen( v ); Q_strncpyz( newInfo.charName, v, ((int)len - (int)strlen(model)) + 1 ); } //Com_Printf("%s\n", newInfo.charName); //slash = strchr( newInfo.modelName, '/' ); if ( !model || !model[1] ) { // modelName didn not include a skin name //Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); Q_strncpyz( newInfo.modelName, "main", sizeof( newInfo.modelName ) ); Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); if ( model && !model[1] ) {//if we had a slash, but nothing after, clear it *model = 0; } } else { model++; //bypass the slash //QVMNOTE len = strlen(model); skin = strchr( model, '/' ); //if there was a model defined, but no skin if ( !skin || !skin[1] ) { //no skin, but I'm guessing we gotz a model at least if ( !skin ) { Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); } else { if ( !skin[1] ) { Q_strncpyz( newInfo.modelName, model, (int)strlen(model) ); } } Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); if ( skin && !skin[1] ) { *skin = 0; } } else { skin++; //QVMNOTE Q_strncpyz( newInfo.modelName, model, ((int)len - (int)strlen(skin)) ); Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); } //Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); // truncate modelName *model = 0; } } //TiM: PMS - age v = Info_ValueForKey( configstring, "age" ); Q_strncpyz( newInfo.age, v, sizeof(newInfo.age) ); //PMS - height v = Info_ValueForKey( configstring, "height" ); newInfo.height = atof( v ); //PMS - weight v = Info_ValueForKey( configstring, "weight" ); newInfo.weight = atof( v ); //PMS - race v = Info_ValueForKey( configstring, "race" ); Q_strncpyz( newInfo.race, v, sizeof(newInfo.race) ); //TiM : Offset for the emote system and solid chairs v = Info_ValueForKey( configstring, "of" ); newInfo.modelOffset = atoi( v ); //CG_Printf( "Set modeloffset as: %f\n", newInfo.modelOffset ); v = Info_ValueForKey( configstring, "admin" ); newInfo.isAdmin = atoi( v ); //ensure the health value is carried over //it normally only gets updated when it itself is changed //newInfo.health = ci->health; //Actually... this might actually screw it up on server start time // 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() < 2000000; // if we are defering loads, just have it pick the first valid if ( forceDefer || ( cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading && ((clientNum != cg.predictedPlayerState.clientNum) && cg.validPPS) ) ) { // keep whatever they had if it won't violate team skins if ( ci->infoValid && ( cgs.gametype < GT_TEAM || !Q_stricmp( newInfo.skinName, ci->skinName ) ) ) { CG_CopyClientInfoModel( ci, &newInfo ); newInfo.deferred = qtrue; } else { // use whatever is available CG_SetDeferredClientInfo( &newInfo, clientNum ); } // 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, clientNum ); } } // replace whatever was there with the new one newInfo.infoValid = qtrue; *ci = newInfo; } /* ====================== 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, i ); // break; } } } /* ====================== CG_NewDecoyInfo TiM: A decoy was spawned, so the relevant data will be set up so it may be displayed independantly of its spawner player. ====================== */ void CG_NewDecoyInfo( int decoyNum ) { clientInfo_t *ci; char *userinfo; int i; char *v; char *temp, *temp2; //char charName[MAX_QPATH], modelName[MAX_QPATH], skinName[MAX_QPATH]; int len; qboolean noMemoryLeft=qfalse; ci = &cgs.decoyInfo[decoyNum]; noMemoryLeft = ( trap_MemoryRemaining() < 4000000 ); //First, check if force player models is on. if so, copy all the data from us to the decoy. //Or, if we're low on memory, let's do this anyway if ( cg_forceModel.integer || noMemoryLeft ) { *ci = cgs.clientinfo[cg.predictedPlayerState.clientNum]; if ( noMemoryLeft ) CG_Printf( S_COLOR_RED "WARNING: Very little memory remains. Decoy data is being deferred to player's active data.\n" ); ci->infoValid = qtrue; return; } //CG_Printf( S_COLOR_RED "decoy ID: %i\n", decoyNum ); //Get the necessary decoy data userinfo = (char *)CG_ConfigString( CS_DECOYS + decoyNum ); if ( !userinfo || !userinfo[0] ) { //No data, so flush and continue memset( ci, 0, sizeof( clientInfo_t ) ); return; } //CG_Printf( S_COLOR_RED "%s\n", userinfo ); //Get model string v = Info_ValueForKey( userinfo, "model" ); //First thing's first. We need to isolate these into three strings: model, char, skin { //step 1, take the first bit of the string and put it in the charName var. if ( ( temp = strchr( v, '/') ) == NULL ) { //if there's no slash Q_strncpyz( ci->charName, v, sizeof( ci->charName ) ); //just set it } else { //otherwise, isolate the first bit, and copy that len = strlen( v ); Q_strncpyz( ci->charName, v, ((int)len - (int)strlen(temp)) + 1 ); } //Com_Printf("%s\n", newInfo.charName); //slash = strchr( newInfo.modelName, '/' ); if ( !temp || !temp[1] ) { // modelName did not include a skin name //Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); Q_strncpyz( ci->modelName, "main", sizeof( ci->modelName ) ); Q_strncpyz( ci->skinName, "default", sizeof( ci->skinName ) ); if ( temp && !temp[1] ) {//if we had a slash, but nothing after, clear it *temp = 0; } } else { temp++; //bypass the slash len = strlen(temp); temp2 = strchr( temp, '/' ); //if there was a model defined, but no skin if ( !temp2 || !temp2[1] ) { //no skin, but I'm guessing we gotz a model at least if ( !temp2 ) { Q_strncpyz( ci->modelName, temp, sizeof( ci->modelName ) ); } else { if ( !temp2[1] ) { Q_strncpyz( ci->modelName, temp, (int)strlen(temp) ); } } Q_strncpyz( ci->skinName, "default", sizeof( ci->skinName ) ); if ( temp2 && !temp2[1] ) { *temp2 = 0; } } else { temp2++; Q_strncpyz( ci->modelName, temp, ((int)len - (int)strlen(temp2)) ); Q_strncpyz( ci->skinName, temp2, sizeof( ci->skinName ) ); } } } //k... get the additional parms from the config string n' put em here v = Info_ValueForKey( userinfo, "weight" ); ci->weight = atof( v ); v = Info_ValueForKey( userinfo, "height" ); ci->height = atof( v ); v = Info_ValueForKey( userinfo, "moOf" ); ci->modelOffset = atoi( v ); v = Info_ValueForKey( userinfo, "c" ); ci->pClass = atoi( v ); ci->animsFlushed = qtrue; //Okay... if another player actively has the skin we want, let's pilfer that rather than load it like a schmuck rofl. { clientInfo_t *match; for ( i = 0; i < cgs.maxclients; i++ ) { match = &cgs.clientinfo[i]; //We found a match! ^_^ if ( !Q_stricmp( match->charName, ci->charName ) && !Q_stricmp( match->modelName, ci->modelName ) && !Q_stricmp( match->skinName, ci->skinName ) ) { CG_CopyClientInfoModel( match, ci ); ci->infoValid = qtrue; return; } } } //*sigh... guess worse came to worse... we gotta fricken load it. :'( if ( !CG_ParseModelDataFile( ci, ci->charName, ci->modelName, ci->skinName ) ) { CG_Printf( S_COLOR_RED "ERROR: Unable to load character for decoy: %s/%s/%s\n", ci->charName, ci->modelName, ci->skinName ); if ( !CG_ParseModelDataFile( ci, ci->charName, DEFAULT_MODEL, DEFAULT_SKIN ) ) { if (!CG_ParseModelDataFile( ci, cg_defaultChar.string, ci->modelName, ci->skinName ) ) { if ( !CG_ParseModelDataFile( ci, cg_defaultChar.string, DEFAULT_MODEL, DEFAULT_SKIN ) ) { //if we hit this.... oh so bad... O_o if ( !CG_ParseModelDataFile( ci, DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ) ) CG_Error( "DEFAULT_CHAR / model / skin ( %s/%s/%s ) failed to register", DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ); } } } } ci->infoValid = qtrue; } /* ============================================================================= PLAYER ANIMATION ============================================================================= */ /* =============== CG_SetLerpFrameAnimation may include ANIM_TOGGLEBIT =============== */ static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { animation_t *anim; lf->animationNumber = newAnimation; newAnimation &= ~ANIM_TOGGLEBIT; if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) { CG_Error( "Bad animation number: %i", newAnimation ); } //CG_Printf("animIndex: %i\n", ci->animIndex ); //anim = &ci->animations[ newAnimation ]; anim = &cg_animsList[ ci->animIndex ].animations[ newAnimation ]; //CG_Printf(S_COLOR_RED "%i\n", ci->animIndex ); lf->animation = anim; lf->animationTime = lf->frameTime + anim->initialLerp; if ( cg_debugAnim.integer ) { CG_Printf( "Anim: %s (%i)\n", GetStringForID(animTable, newAnimation), newAnimation ); //CG_Printf( "Anim: %i\n", newAnimation ); } } /* =============== CG_RunLerpFrame Sets cg.snap, cg.oldFrame, and cg.backlerp cg.time should be between oldFrameTime and frameTime after exit =============== */ static qboolean CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { int f; animation_t *anim; qboolean newFrame = qfalse; float frameLerp; // debugging tool to get no animations if ( cg_animSpeed.integer == 0 ) { lf->oldFrame = lf->frame = lf->backlerp = 0; return qfalse; } // see if the animation sequence is switching if ( newAnimation != lf->animationNumber || !lf->animation ) { CG_SetLerpFrameAnimation( ci, lf, newAnimation ); } // if we have passed the current frame, move it to // oldFrame and calculate a new frame if ( cg.time >= lf->frameTime ) { lf->oldFrame = lf->frame; lf->oldFrameTime = lf->frameTime; // get the next frame based on the animation anim = lf->animation; if ( !anim || !anim->frameLerp ) { return qfalse; // shouldn't happen } //RPG-X Check. Anims with lengths < 0 are emote stubs. //If we get one, hardcode to override to default standing. //Otherwise, we'll get complicated glitches. if ( anim->numFrames < 0 ) { CG_SetLerpFrameAnimation( ci, lf, BOTH_STAND1 ); } //TiM - Calc frame lerp scale here, else the frames //just snap to each other frameLerp = (float)anim->frameLerp + (anim->frameLerp*(1.0f - speedScale)); if ( frameLerp < 1.0f ) frameLerp = 1.0f; //CG_Printf( "Lerp: %f\n", frameLerp ); if ( cg.time < lf->animationTime ) { lf->frameTime = lf->animationTime; // initial lerp } else { lf->frameTime = lf->oldFrameTime + frameLerp;//anim->frameLerp; } f = ( lf->frameTime - lf->animationTime ) / frameLerp;//anim->frameLerp; //f *= speedScale; // adjust for haste, etc if ( f >= anim->numFrames ) { f -= anim->numFrames; if ( anim->loopFrames ) { f %= anim->loopFrames; f += anim->numFrames - anim->loopFrames; } else { f = anim->numFrames - 1; // the animation is stuck at the end, so it // can immediately transition to another sequence lf->frameTime = cg.time; } } lf->frame = anim->firstFrame + f; if ( cg.time > lf->frameTime ) { lf->frameTime = cg.time; if ( cg_debugAnim.integer ) { CG_Printf( "Clamp lf->frameTime\n"); } } newFrame = qtrue; } if ( lf->frameTime > cg.time + 200 ) { lf->frameTime = cg.time; } if ( lf->oldFrameTime > cg.time ) { lf->oldFrameTime = cg.time; } // calculate current lerp value if ( lf->frameTime == lf->oldFrameTime ) { lf->backlerp = 0; } else { lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); } return newFrame; } /* =============== CG_ClearLerpFrame =============== */ //This function has been rpg-x'edŽ! (by RedTechie) void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { //RPG-X: RedTechie - Changed type from static void to void if(!lf) return; lf->frameTime = lf->oldFrameTime = cg.time; lf->animation = 0; CG_SetLerpFrameAnimation( ci, lf, animationNumber ); lf->oldFrame = lf->frame = lf->animation->firstFrame; //RPG-X: RedTechie - Added this block of code /*if ( lf->animation->frameLerp < 0 ) {//Plays backwards lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames); } else { lf->oldFrame = lf->frame = lf->animation->firstFrame; }*/ //TiM - Put in, for now } /* =============== CG_PlayerAnimation =============== */ extern qboolean PM_PlayerWalking( int anim ); extern qboolean PM_PlayerRunning( int anim ); extern qboolean PM_PlayerCrouchWalking( int anim ); 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=1; qboolean newLegsFrame = qfalse; qboolean newTorsoFrame = qfalse; //float speed; qboolean isDecoy = cent->currentState.eFlags & EF_ITEMPLACEHOLDER; clientNum = cent->currentState.clientNum; if ( cg_noPlayerAnims.integer ) { *legsOld = *legs = *torsoOld = *torso = 0; return; } //if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { // speedScale = 1.5; //} else { // speedScale = 1; //} //CG_Printf( "Vel: %f\n", VectorLength(cent->currentState.pos.trDelta)); //TiM // 250 = default running speed // 125 = default walking speed // 90 = default crouchwalk speed //if ( !isDecoy ) //{ // if ( clientNum == cg.snap->ps.clientNum ) // speed = VectorLength( cg.predictedPlayerState.velocity ); // else // speed = VectorLength(cent->currentState.pos.trDelta); // //if ( speed < 50.0f ) // //speed = 50.0f; // if ( PM_PlayerWalking( cent->currentState.legsAnim ) ) // { // speedScale = speed / 125.0f; // } // else if ( PM_PlayerRunning( cent->currentState.legsAnim ) ) // { // speedScale = speed / 250.0f; // } // else if ( PM_PlayerCrouchWalking( cent->currentState.legsAnim ) ) // { // speedScale = speed / 90.0f; // } // else // { // speedScale = 1.0f; // } //} if ( isDecoy ) ci = &cgs.decoyInfo[ cent->currentState.eventParm ]; else ci = &cgs.clientinfo[ clientNum ]; // do the shuffle turn frames locally if ( !cent->clampAngles && cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND1 ) { //TORSO_STAND newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN1, speedScale ); } else { newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); } if( newLegsFrame && ci->animSndIndex >= 0 && !(cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) { trace_t tr; vec3_t endPoint; //qboolean metal = qfalse; //Uberhack meant specifically for metal clank surfaces vec3_t mins = { -16, -16, 0 }; vec3_t maxs = { 16, 16, 0 }; int surfType; //TiM: Lower based sounds are always to do with things like shoes clopping n' stuff. //This portion of code makes sure the player is on a solid surface in order to play this sound VectorCopy( cent->lerpOrigin, endPoint); endPoint[2] -= 24.50f; CG_Trace( &tr, cent->lerpOrigin, mins, maxs, endPoint, cent->currentState.clientNum, MASK_PLAYERSOLID | CONTENTS_LADDER ); //trap_CM_BoxTrace( &tr, cent->lerpOrigin, endPoint, mins, maxs, 0, MASK_PLAYERSOLID ); //metal = tr.surfaceFlags & SURF_METALSTEPS || (tr.contents & CONTENTS_LADDER); if(tr.surfaceFlags & SURF_METALSTEPS || (tr.contents & CONTENTS_LADDER)) surfType = 1; else if(tr.surfaceFlags & SURF_GRASS) surfType = 2; else if(tr.surfaceFlags & SURF_GRAVEL) surfType = 3; else if(tr.surfaceFlags & SURF_SNOW) surfType = 4; else if(tr.surfaceFlags & SURF_WOOD) surfType = 5; else surfType = 0; //if there's something below us, or we're free floating in something like water/lava/slime if ( tr.fraction != 1.0f || (tr.contents & MASK_WATER ) || (tr.contents & CONTENTS_LADDER) ) { CG_PlayerAnimSounds( cg_animsSndList[ci->animSndIndex].lowerAnimSounds, cent->pe.legs.frame, cent->currentState.clientNum, /*metal*/surfType ); } } *legsOld = cent->pe.legs.oldFrame; *legs = cent->pe.legs.frame; *legsBackLerp = cent->pe.legs.backlerp; //if ( PM_PlayerWalking( cent->currentState.torsoAnim ) ) //{ // speedScale *= speed / 125.0f; //} //else if ( PM_PlayerRunning( cent->currentState.torsoAnim ) ) //{ // speedScale *= speed / 250.0f; //} //else if ( PM_PlayerCrouchWalking( cent->currentState.torsoAnim ) ) //{ // speedScale *= speed / 90.0f; //} //else //{ // speedScale = 1.0f; //} newTorsoFrame = CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); if( newTorsoFrame && ci->animSndIndex >= 0 ) { CG_PlayerAnimSounds( cg_animsSndList[ci->animSndIndex].upperAnimSounds, cent->pe.torso.frame, cent->currentState.clientNum, /*qfalse*/0 ); } *torsoOld = cent->pe.torso.oldFrame; *torso = cent->pe.torso.frame; *torsoBackLerp = cent->pe.torso.backlerp; } /* ============================================================================= PLAYER ANGLES ============================================================================= */ /* ================== CG_SwingAngles ================== */ static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, float speed, float *angle, qboolean *swinging, qboolean pitch ) { float swing; float move; float scale; swing = AngleSubtract( destination, *angle ); /*if ( canSwing ) { if ( swing == 0 ) { *swinging = qfalse; } else if ( swing >= clampTolerance || swing <= -clampTolerance ) { *swinging = qtrue; } else { *swinging = qtrue; } } else {*/ if ( !*swinging ) { // see if a swing should be started //swing = AngleSubtract( *angle, destination ); if ( swing == 0 ) { *swinging = qfalse; } else if ( !pitch && ( swing >= clampTolerance || swing <= -clampTolerance ) ) { *swinging = qtrue; } if ( pitch ) *swinging = qtrue; /*else if ( swing > swingTolerance || swing < -swingTolerance ) { *swinging = qtrue; }*/ } //} if ( !*swinging ) { return; } // modify the speed depending on the delta // so it doesn't seem so linear swing = AngleSubtract( destination, *angle ); scale = fabs( swing ); if ( scale < swingTolerance * 0.5 ) { scale = 0.5; } else if ( scale < swingTolerance ) { scale = 1.0; } else { scale = 2.0; } // swing towards the destination angle if ( swing >= 0 ) { move = cg.frametime * scale * speed; if ( move >= swing ) { move = swing; *swinging = qfalse; } *angle = AngleMod( *angle + move ); } else if ( swing < 0 ) { move = cg.frametime * scale * -speed; if ( move <= swing ) { move = swing; *swinging = qfalse; } *angle = AngleMod( *angle + move ); } // clamp to no more than tolerance swing = AngleSubtract( destination, *angle ); if ( swing > clampTolerance ) { *angle = AngleMod( destination - (clampTolerance - 1) ); //clampTolerance } else if ( swing < -clampTolerance ) { *angle = AngleMod( destination + (clampTolerance - 1) ); //clampTolerance } } /* ================= 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 float yawClamped; //static float headClamp; #define YAW_DELTA 100 //max yaw a head can turn around without looking like an exorcist spoof ;P #define PITCH_DELTA 35 //max pitch a head can tilt before looking like the player sepearated their neck O_o extern qboolean PM_PlayerIdling ( int torsoAnim, int legsAnim ); 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; //float delta; static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; //{ 0, 22, 45, -22, 0, 22, -45, -22 }; vec3_t velocity; float speed; int dir; qboolean offsetPitch; clientInfo_t* ci; int i; qboolean LockBodyYaw=qfalse; //RPG-X:TiM if ( cent->currentState.eFlags & EF_ITEMPLACEHOLDER ) ci = &cgs.decoyInfo[cent->currentState.eventParm]; else ci = &cgs.clientinfo[cent->currentState.clientNum]; if ( cent->currentState.eFlags & EF_CLAMP_ALL ) { VectorSet( headAngles, 0, cent->pe.legs.yawAngle, 0 ); VectorSet( torsoAngles, 0, cent->pe.legs.yawAngle, 0 ); VectorSet( legsAngles, 0, cent->pe.legs.yawAngle, 0 ); AnglesSubtract( headAngles, torsoAngles, headAngles ); AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); AnglesToAxis( legsAngles, legs ); AnglesToAxis( torsoAngles, torso ); AnglesToAxis( headAngles, head ); return; } if( /*( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_LADDER_DWN1 && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_LADDER_UP1 && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_LADDER_IDLE &&*/ !( cent->currentState.eFlags & EF_CLAMP_BODY ) ) { LockBodyYaw = qfalse; } else { LockBodyYaw = qtrue; cent->pe.torso.pitchAngle = 0; } //} VectorCopy( cent->lerpAngles, headAngles ); VectorClear( legsAngles ); VectorClear( torsoAngles ); headAngles[YAW] = AngleMod( headAngles[YAW] ); //headAngles[PITCH] = AngleMod( headAngles[PITCH] ); /*if ( LockBodyYaw && yawClamped == 0.0 ) yawClamped = headAngles[YAW]; else if ( !LockBodyYaw ) { yawClamped = 0.0; }*/ if ( LockBodyYaw ) { float deltaYaw; float turnRatio; float finalPitch; //calc ratio of delta from origin yaw to current yaw turnRatio = Q_fabs( AngleDelta( cent->pe.torso.yawAngle, headAngles[YAW] ) ) / 90.0f; if ( turnRatio > 1.0f ) turnRatio = 1.0f; if ( turnRatio < 0.0f ) turnRatio = 0.0f; finalPitch = (float)PITCH_DELTA - ( 10.0f * turnRatio ); //CG_Printf( "Pitch Before: %f\n", headAngles[PITCH] ); //handle head pitch //if players are looking down, clamp it so the more yaw there is, the higher they'll be looking. //reason being, most humans can't bury their face that far into their shoulders. if ( headAngles[PITCH] > finalPitch && headAngles[PITCH] > 0 ) //Looking down. weirdly enough { headAngles[PITCH] = finalPitch; } else if ( headAngles[PITCH] < -PITCH_DELTA ) { //delta = headAngles[YAW] + AngleMod( cent->pe.legs.yawAngle + YAW_DELTA); //headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle - YAW_DELTA) + delta; headAngles[PITCH] = -PITCH_DELTA; } //if head yaw is about to rip off the exorcist /*if ( ( headAngles[YAW] > AngleMod( cent->pe.legs.yawAngle + YAW_DELTA) ) && ( headAngles[YAW] < AngleMod( cent->pe.legs.yawAngle + 180.0f) ) ) { //delta = Q_fabs(headAngles[YAW] - AngleMod( cent->pe.legs.yawAngle + YAW_DELTA)); //headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle + YAW_DELTA) - delta; headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle + YAW_DELTA); } else if ( headAngles[YAW] < AngleMod( cent->pe.legs.yawAngle - YAW_DELTA) && ( headAngles[YAW] > AngleMod( cent->pe.legs.yawAngle + 180.0f) ) ) { //delta = headAngles[YAW] + AngleMod( cent->pe.legs.yawAngle + YAW_DELTA); //headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle - YAW_DELTA) + delta; headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle - YAW_DELTA); }*/ if ( Q_fabs( deltaYaw = AngleDelta( headAngles[YAW], cent->pe.torso.yawAngle ) ) > YAW_DELTA ) { if ( deltaYaw > 0 ) { headAngles[YAW] = AngleNormalize360 ( cent->pe.torso.yawAngle + YAW_DELTA ); } else { headAngles[YAW] = AngleNormalize360 ( cent->pe.torso.yawAngle - YAW_DELTA ); } } //CG_Printf( "Pitch After: %f\n", headAngles[PITCH] ); } // --------- yaw ------------- // allow yaw to drift a bit if ( !PM_PlayerIdling( cent->currentState.torsoAnim, cent->currentState.legsAnim ) || ( cg_liftEnts[cent->currentState.clientNum] > 0/*(cg.time - cgs.levelStartTime)*/ ) ) { // 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 offsetPitch = qfalse; } else { offsetPitch = qtrue; } // 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" ); } } //RPG-X Ladder disables character yawing coz it's like they're spinning on air if( !LockBodyYaw ) { legsAngles[YAW] = headAngles[YAW] ; torsoAngles[YAW] = headAngles[YAW] ; if ( !cg_liftEnts[cent->currentState.clientNum] ) { legsAngles[YAW] += movementOffsets[ dir ]; torsoAngles[YAW] += 0.25 * movementOffsets[ dir ]; } CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing, qfalse ); CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing, qfalse ); } //TiM - if turbolifting, rotate us, but then don't lerp if ( cg_liftEnts[cent->currentState.clientNum] > 0/*(cg.time - cgs.levelStartTime)*/ ) { if ( !cent->pe.legs.yawing ) { cent->clampAngles = qtrue; } } if ( cent->clampAngles ) { //torsoAngles[YAW] = headAngles[YAW]; //legsAngles[YAW] = headAngles[YAW]; cent->pe.torso.yawAngle = headAngles[YAW]; cent->pe.legs.yawAngle = headAngles[YAW]; } torsoAngles[YAW] = cent->pe.torso.yawAngle; legsAngles[YAW] = cent->pe.legs.yawAngle; if ( !cg_liftEnts[cent->currentState.clientNum] && cent->clampAngles > qfalse ) cent->clampAngles = qfalse; // --------- pitch ------------- //TiM : Add an offset so they don't lean as much when idling //Make it default elsewise tho // only show a fraction of the pitch angle in the torso //Com_Printf( "headPitch: %f\n", headAngles[PITCH] ); if ( headAngles[PITCH] > 180 ) { dest = (-360 + headAngles[PITCH]) * (offsetPitch==qtrue ? 0.45 : 0.75); //(offsetPitch ? 0.95 : 0.75) } else { //if offsetPitch enabled (ie, we're in the ideal pose for the cool neck-only rotation) //this will make the actual torso pitch delay a tad until it's passed a threshold //of 30 degrees in either direction. The overall aim of this is to try and minimize //the torso movement so it's more realistically subtle, instead of stupidly, physics defyingly //obvious (like being able to bend on a 90 degree ange >_< ) if ( offsetPitch ) { if ( headAngles[PITCH] > 30 ) { dest = (headAngles[PITCH] - 30 ) * 0.60; } else if ( headAngles[PITCH] < -40 ) { dest = ( headAngles[PITCH] + 40 ) * 0.45; } else { dest = 0; } } else { dest = headAngles[PITCH] * 0.75; } } //I had to lock down the pitch when dead. The player's head was going thru the floor O_o if( !LockBodyYaw && !( cent->currentState.eFlags & EF_DEAD ) /*&& !( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )*/ ) { //(offsetPitch ? 10 : 30) if (cent->currentState.eFlags & EF_FULL_ROTATE ) { //CG_Printf("Lerp Detected\n"); legsAngles[PITCH] = headAngles[PITCH]; CG_SwingAngles( legsAngles[PITCH], 15, 30, 0.1, ¢->pe.legs.pitchAngle, ¢->pe.legs.pitching, qtrue ); legsAngles[PITCH] = cent->pe.legs.pitchAngle; torsoAngles[PITCH] = headAngles[PITCH]; CG_SwingAngles( torsoAngles[PITCH], 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching, qtrue ); torsoAngles[PITCH] = cent->pe.torso.pitchAngle; } else { CG_SwingAngles( dest, 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching, qtrue ); torsoAngles[PITCH] = cent->pe.torso.pitchAngle; } } //Com_Printf("Bitwise operation >>: %i, Bitwise Operation &: %i\n", Q_log2(8192), 10 & 255 ); //Com_Printf("Atof: %f\n", atof( "0.43284673 t" ) ); // --------- talking ---------- // ------ head rotation ------- if ( cent->currentState.eFlags & EF_TALKING ) { ci->headDebounceTime = cg.time + 1000; } if (ci->headDebounceTime > cg.time /*&& !LockBodyYaw*/ ) { if ( cent->currentState.eFlags & EF_TALKING ) { if ( cg.time > ci->nextTalkAngle || (!ci->talkAngles[PITCH] && !ci->talkAngles[YAW] && !ci->talkAngles[ROLL]) ) { for ( i = 0; i < 3; i++ ) { ci->talkAngles[i] = flrandom( -4, 4 ); } ci->talkDifferential = irandom( 200, 500 ); ci->nextTalkAngle = cg.time + ci->talkDifferential; } } else { if ( ci->talkAngles[0] != 1 && ci->talkAngles[1] != 1 && ci->talkAngles[2] != 1 ) { //VectorCopy(ci->talkAngles, cent->lerpAngles); ci->talkDifferential = 300; VectorSet(ci->talkAngles, 0, 0, 0 ); } } //Com_Printf("Yaw Offset: %f, Yaw: %f, LerpYaw: %f\n", ci->talkAngles[YAW], headAngles[YAW], cent->lerpAngles[YAW]); //if ( (cent->pe.head.pitchAngle != 0.0 && cent->pe.head.yawAngle != 0.0 && cent->pe.head.yawAngle != 0.0) // && !VectorCompare( ci->talkAngles, vec3_origin) ) { CG_SwingAngles( ci->talkAngles[PITCH], 30, 30, ci->talkDifferential*0.00005, ¢->pe.head.pitchAngle, ¢->pe.head.pitching, qtrue ); CG_SwingAngles( ci->talkAngles[YAW], 30, 30, ci->talkDifferential*0.00005, ¢->pe.head.yawAngle, ¢->pe.head.yawing, qtrue ); CG_SwingAngles( ci->talkAngles[ROLL], 30, 30, ci->talkDifferential*0.00005, ¢->pe.head.rollAngle, ¢->pe.head.rolling, qtrue ); headAngles[PITCH] = AngleMod(headAngles[PITCH] + cent->pe.head.pitchAngle); headAngles[YAW] = AngleMod(headAngles[YAW] + cent->pe.head.yawAngle); headAngles[ROLL] = AngleMod(headAngles[ROLL] + cent->pe.head.rollAngle); //} } // --------- roll ------------- //TiM - After I reintegrated velocity into //the player state, this code randomly started working lol! // 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.04; //0.05 - TiM AnglesToAxis( legsAngles, axis ); side = speed * DotProduct( velocity, axis[1] ); legsAngles[ROLL] -= side; side = speed * DotProduct( velocity, axis[0] ); legsAngles[PITCH] += side; } // pain twitch CG_AddPainTwitch( cent, torsoAngles ); // pull the angles back out of the hierarchial chain AnglesSubtract( headAngles, torsoAngles, headAngles ); AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); AnglesToAxis( legsAngles, legs ); AnglesToAxis( torsoAngles, torso ); AnglesToAxis( headAngles, head ); } //========================================================================== /* =============== CG_HasteTrail =============== */ /*static void CG_HasteTrail( centity_t *cent ) { localEntity_t *smoke; vec3_t origin, pos2; int anim; if ( cent->trailTime > cg.time ) { return; } anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; if ( anim != BOTH_RUN1 && anim != BOTH_RUN1 ) { //LEGS_RUN return; } cent->trailTime += 100; if ( cent->trailTime < cg.time ) { cent->trailTime = cg.time; } VectorCopy( cent->lerpOrigin, origin ); origin[0] += flrandom(-5,5); origin[1] += flrandom(-5,5); origin[2] -= 15; AngleVectors(cent->lerpAngles, pos2, NULL, NULL); pos2[2]=0; VectorMA(origin, -22.0, pos2, pos2); smoke = FX_AddLine(origin, pos2, 1.0, 20.0, -12.0, 0.7, 0.0, 500, cgs.media.hastePuffShader); }*/ /* =============== CG_FlightTrail =============== */ /*static void CG_FlightTrail( centity_t *cent ) { localEntity_t *smoke; vec3_t origin; vec3_t vel; vec3_t startrgb={0.5, 0.5, 0.5}; vec3_t endrgb={0.0,0.0,0.0}; VectorCopy( cent->lerpOrigin, origin ); origin[2] -= flrandom(10,18); VectorSet(vel, flrandom(-10,10), flrandom(-10, 10), flrandom(-30,-50)); smoke = FX_AddSprite2(origin, vel, qfalse, 4.0, 4.0, 0.5, 0.0, startrgb, endrgb, flrandom(0,360), 0, 500, cgs.media.flightPuffShader); }*/ /* =============== CG_TrailItem =============== */ /*static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { refEntity_t ent; vec3_t angles; float frame; if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { return; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpAngles, angles ); angles[PITCH] = 0; angles[ROLL] = 0; angles[YAW] += 180.0; // It's facing the wrong way. AnglesToAxis( angles, ent.axis ); VectorMA( cent->lerpOrigin, 12, ent.axis[0], ent.origin ); ent.origin[2] += 4; // Make it animate. frame = (cg.time / 100.0); ent.renderfx|=RF_WRAP_FRAMES; ent.oldframe = (int)frame; ent.frame = (int)frame+1; ent.backlerp = (float)(ent.frame) - frame; // if the player is looking at himself in 3rd person, don't show the flag solid, 'cause he can't see!!! if (cent->currentState.number == cg.snap->ps.clientNum) { ent.shaderRGBA[3] = 128; ent.renderfx |= RF_FORCE_ENT_ALPHA; } VectorScale(ent.axis[0], 0.75, ent.axis[0]); VectorScale(ent.axis[1], 0.9, ent.axis[1]); VectorScale(ent.axis[2], 0.9, ent.axis[2]); ent.nonNormalizedAxes = qtrue; #if 0 // This approach is used if you want the item to autorotate. Since this is the flag, we don't. VectorScale( cg.autoAxis[0], 0.75, ent.axis[0] ); VectorScale( cg.autoAxis[1], 0.75, ent.axis[1] ); VectorScale( cg.autoAxis[2], 0.75, ent.axis[2] ); #endif ent.hModel = hModel; trap_R_AddRefEntityToScene( &ent ); }*/ /* =============== CG_PlayerPowerups =============== */ static void CG_PlayerPowerups( centity_t *cent ) { int powerups; 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.2, 0.2, 1.0 ); }*/ // invul gives a dlight /* if ( powerups & ( 1 << PW_BOLTON ) ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.8, 0.8, 0.2 ); }*/ // borg adapt gives a dlight //RPG-X TiM /*if ( powerups & ( 1 << PW_BEAMING ) ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 1.0, 0.2 ); }*/ // flight plays a looped sound // if ( powerups & ( 1 << PW_FLIGHT ) ) { // trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); // } // redflag /*if ( powerups & ( 1 << PW_REDFLAG ) ) { CG_TrailItem( cent, cgs.media.redFlagModel ); trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1, 0.2, 0.2 ); }*/ // blueflag | RGP-X | GSIO01 | 08/05/2009: no blueflag no more... now borgadapt if ( powerups & ( 1 << PW_BORG_ADAPT ) ) { //CG_TrailItem( cent, cgs.media.blueFlagModel ); //trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1 ); trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 1.0, 0.2 ); } // haste leaves smoke trails /*if ( powerups & ( 1 << PW_HASTE ) && (cent->currentState.groundEntityNum==ENTITYNUM_WORLD)) { CG_HasteTrail( cent ); }*/ // haste leaves smoke trails // if ( powerups & ( 1 << PW_FLIGHT ) && (cent->currentState.groundEntityNum!=ENTITYNUM_WORLD)) { // CG_FlightTrail( cent ); // } // seeker coolness /*if ( powerups & ( 1 << PW_FLASHLIGHT ) ) { CG_Seeker(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; int team; if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { rf = RF_THIRD_PERSON; // only show in mirrors } else { rf = 0; } team = cgs.clientinfo[ cent->currentState.clientNum ].team; memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); ent.origin[2] += 48; ent.reType = RT_SPRITE; ent.customShader = shader; ent.data.sprite.radius = 10; ent.renderfx = rf; if (team==TEAM_RED) { ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 64; ent.shaderRGBA[2] = 64; } else if (team==TEAM_BLUE) { ent.shaderRGBA[0] = 64; ent.shaderRGBA[1] = 64; ent.shaderRGBA[2] = 255; } else { ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; } ent.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_PlayerSprites Float sprites over the player's head =============== */ static void CG_PlayerSprites( centity_t *cent ) { // int team; if ( cent->currentState.eFlags & EF_CONNECTION ) { CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); return; } /* if ( cent->currentState.eFlags & EF_TALK ) { if ( cgs.clientinfo[cent->currentState.number].pClass == PC_ACTIONHERO ) { CG_PlayerFloatSprite( cent, cgs.media.heroSpriteShader ); } if ( cgs.clientinfo[cent->currentState.number].pClass == PC_BORG ) { if ( (cg_entities[cent->currentState.number].currentState.powerups&(1<currentState.number].pClass == PC_ACTIONHERO ) { CG_PlayerFloatSprite( cent, cgs.media.heroSpriteShader ); return; } //Special hack: if it's Borg who has regen going, must be Borg queen if ( cgs.clientinfo[cent->currentState.number].pClass == PC_BORG ) { if ( (cg_entities[cent->currentState.number].currentState.powerups&(1<currentState.number].pClass == PC_BORG ) { CG_PlayerFloatSprite( cent, cgs.media.borgIconShader ); return; } if ( cent->currentState.eFlags & EF_AWARD_FIRSTSTRIKE ) { CG_PlayerFloatSprite( cent, cgs.media.medalFirstStrike ); 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_ACE ) { CG_PlayerFloatSprite( cent, cgs.media.medalAce ); return; } if ( cent->currentState.eFlags & EF_AWARD_EXPERT ) { CG_PlayerFloatSprite( cent, cgs.media.medalExpert ); return; } if ( cent->currentState.eFlags & EF_AWARD_MASTER ) { CG_PlayerFloatSprite( cent, cgs.media.medalMaster ); return; } if ( cent->currentState.eFlags & EF_AWARD_CHAMPION ) { CG_PlayerFloatSprite( cent, cgs.media.medalChampion ); 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 && cent->currentState.number != cg.snap->ps.clientNum ) // Don't show a sprite above a player's own head in 3rd person. { if (team==TEAM_RED) { CG_PlayerFloatSprite( cent, cgs.media.teamRedShader ); } else if (team==TEAM_BLUE) { CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader ); } // else don't show an icon. There currently are no other team types. return; }*/ //RPG-X: RedTechie - Cloak sprite basiclly other admins will see this only to tell if that player is cloaked and a admin //if( cent->currentState.powerups & ( 1 << PW_INVIS ) ){ // CG_PlayerFloatSprite( cent, cgs.media.cloakspriteShader ); // return; //} return; } /* =============== CG_CalcBeamAlpha By TiM Calculates the current point in a transport cycle so we can use it as a fade percentage Used in shadows, and on the player model itself =============== */ #define PLAYER_BEAM_FADETIME_DIV 1.0/(float)PLAYER_BEAM_FADETIME void CG_CalcBeamAlpha( int powerups, beamData_t *beamData ) { float beamAlpha = 1.0; int bTime = 0; if ( ( powerups & ( 1 << PW_BEAM_OUT ) ) || ( powerups & ( 1 << PW_QUAD ) ) ) { //TiM - SP transporter FX, also base alpha off of phase in transport cycle //bTime = cg.time - beamData->beamTimeParam; bTime = cg.time - beamData->beamTimeParam; if (bTime > PLAYER_BEAM_FADE ) { if ( bTime < ( PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME) ) { beamAlpha = (float)( bTime - PLAYER_BEAM_FADE ) * PLAYER_BEAM_FADETIME_DIV; //if we're beaming out, invert the alpha value (so we fade out, not in ) if ( powerups & ( 1 << PW_BEAM_OUT ) ) { beamAlpha = 1.0 - beamAlpha; } //Com_Printf( "Alpha = %f\n", beamAlpha ); } else { if ( powerups & ( 1 << PW_BEAM_OUT ) ) { beamAlpha = 0.0; } else { beamAlpha = 1.0; } } } else { if ( powerups & ( 1 << PW_BEAM_OUT ) ) { beamAlpha = 1.0; } else { beamAlpha = 0.0; } } //CG_Printf( "BeamTime: %i, Alpha: %f\n", bTime, beamAlpha ); } beamData->beamAlpha = beamAlpha; } /* =============== 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, vec3_t origin, float *shadowPlane, float sizeOffset ) { vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2}; trace_t trace; float alpha; *shadowPlane = 0; if ( cg_shadows.integer == 0 ) { return qfalse; } // no shadows when invisible //TiM - handled in two phases. When invis powerup is active, and beyond flash time, or invis is inactvie, and before flashtime if ( ( ( cent->currentState.powerups & ( 1 << PW_INVIS ) && cent->cloakTime > 0 && cg.time > cent->cloakTime + Q_FLASH_TIME * 0.5 ) || ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) && cent->decloakTime > 0 && cg.time < cent->decloakTime + Q_FLASH_TIME * 0.5 ) ) ) { return qfalse; } // send a trace down from the player to the ground //VectorCopy( cent->lerpOrigin, end ); VectorCopy( origin, end ); end[2] -= SHADOW_DISTANCE; //trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); trap_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID ); // no shadow if too high if ( trace.fraction == 1.0 ) { return qfalse; } *shadowPlane = trace.endpos[2] + 1; if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows return qtrue; } // fade the shadow out with height alpha = 1.0 - trace.fraction; //transporter FX - beam alpha alpha *= cent->beamData.beamAlpha; if ( alpha == 0.0 ) { return qfalse; } // --TiM // add the mark as a temporary, so it goes directly to the renderer // without taking a spot in the cg_marks array CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, 1,1,1,alpha, qfalse, ( 16 * sizeOffset ), 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]; float beamRatio = 1.0; 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; } if ( (cent->currentState.powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time > ( cent->beamData.beamTimeParam + 2000 ) ) ) { //beamRatio = 1.0f - (( (float)cg.time - ( (float)cent->beamData.beamTimeParam + 2000.0f ) ) / 2000.0f); beamRatio = 1.0f - (( (float)cg.time - ( (float)cent->beamData.beamTimeParam + 2000.0f ) ) * 0.0005f); //CG_Printf( "Beam Out: %f\n", beamRatio ); } if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { //beamRatio = ( ( (float)cg.time - (float)cent->beamData.beamTimeParam ) / 2000.0f ); beamRatio = ( ( (float)cg.time - (float)cent->beamData.beamTimeParam ) * 0.0005f ); //CG_Printf( "Beam In: %f\n", beamRatio ); } if ( beamRatio > 1.0 ) { beamRatio = 1.0; } if ( beamRatio < 0.0 ) { beamRatio = 0.0; } // create a mark polygon VectorCopy( trace.endpos, verts[0].xyz ); verts[0].xyz[0] -= 32 * beamRatio; verts[0].xyz[1] -= 32 * beamRatio; 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 * beamRatio; verts[1].xyz[1] += 32 * beamRatio; 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 * beamRatio; verts[2].xyz[1] += 32 * beamRatio; 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 * beamRatio; verts[3].xyz[1] -= 32 * beamRatio; 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 ); } static int timestamp; /* =============== CG_AddRefEntityWithPowerups Adds a piece with modifications or duplications for powerups Also called by CG_Missile for quad rockets, but nobody can tell... =============== */ void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int eFlags, beamData_t *beamData, int cloakTime, int decloakTime, qboolean borg ) { //TiM : No more flickering. Flickering Starfleet officers is bad /*if ( eFlags & EF_ITEMPLACEHOLDER ) // Hologram Decoy { float f1, f2; // We used EF_ITEMPLACEHOLDER flag to indicate that this 'player' model // is actually a holographic decoy. Now there is a chance that the // decoy will flicker a bit because of ordering with alpha shaders... // The lowest the alpha goes is 4.0-2.5-1.0=0.5. f1 = 4.0 + 2.5*sin(52.423 + cg.time/205.243); f2 = sin(14.232 + cg.time/63.572); f1 = f1+f2; if (f1 > 1.0) { // Just draw him solid. if ( cg.snap->ps.persistant[PERS_CLASS] == PC_TECH ) {//technicians can see decoys as grids ent->customShader = cgs.media.rezOutShader; ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 128; } trap_R_AddRefEntityToScene( ent ); } else { // Draw him faded. if (f1 > 0.8) { // Don't have alphas over 0.8, it just looks bad. f1=0.8; } else if (f1 < 0.1) { f1=0.1; } ent->renderfx |= RF_FORCE_ENT_ALPHA; // Override the skin shader info and use this alpha value. ent->shaderRGBA[3] = 255.0*f1; trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; // ...with a static shader. ent->customShader = cgs.media.holoDecoyShader; ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255.0*(1.0-f1); // More solid as the player fades out... trap_R_AddRefEntityToScene(ent); } return; }*/ if ((eFlags & EF_DEAD) && (timestamp > cg.time)) { // Dead. timestamp holds the time of death. float alpha; int a; // First draw the entity itself. //alpha = (timestamp - cg.time)/2500.0; alpha = (timestamp - cg.time) * 0.0004; ent->renderfx |= RF_FORCE_ENT_ALPHA; a = alpha * 255.0; if (a <= 0) a=1; ent->shaderRGBA[3] = a; trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; // Now draw the static shader over it. // Alpha in over half the time, out over half. alpha = sin(M_PI*alpha); a = alpha * 255.0; if (a <= 0) a=1; ent->customShader = cgs.media.rezOutShader; ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = a; trap_R_AddRefEntityToScene( ent ); ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255; } else if( powerups & ( 1 << PW_BORG_ADAPT ) ) { ent->renderfx |= RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; trap_R_AddRefEntityToScene(ent); ent->customShader = cgs.media.borgFullBodyShieldShader; trap_R_AddRefEntityToScene(ent); return; } else if ( powerups & ( 1 << PW_INVIS ) || ( !(powerups & ( 1 << PW_INVIS )) && decloakTime > 0 ) ) { if ( ( cloakTime <= 0 && decloakTime <= 0 ) || ( decloakTime > 0 && cg.time < ( decloakTime + Q_FLASH_TIME * 0.5 ) ) || ( cloakTime > 0 && cg.time > ( cloakTime + Q_FLASH_TIME * 0.5 ) ) ) { if ( cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/ ) {//admins can see cloaked people //RPG-X: RedTechie - Pretty Admin Stuff //ent->customShader = cgs.media.teleportEffectShader; ent->renderfx |= RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = (unsigned char)(0.4f * 255.0f); trap_R_AddRefEntityToScene( ent ); } else return; //ent->customShader = cgs.media.invisShader; //TiM : No point since it's a 100% transparent shader. Use the EF_NODRAW flag instead } else trap_R_AddRefEntityToScene( ent ); } else if (powerups & (1<renderfx |= RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255 - (dtime)*0.25; trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; } if (dtime < 2000) { ent->customShader = cgs.media.disruptorShader; //ent->shaderTime = timeParam / 1000.0f; ent->shaderTime = timeParam * 0.001f; trap_R_AddRefEntityToScene( ent ); } } else if (powerups & (1<renderfx |= RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = (int)(255.0 - (dtime / 300.0) * 254.0); trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; } if (dtime < 500) { ent->customShader = cgs.media.explodeShellShader; ent->renderfx |= RF_CAP_FRAMES; //ent->shaderTime = timeParam / 1000.0f; ent->shaderTime = timeParam * 0.001f; trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_CAP_FRAMES; } } else if (powerups & (1<renderfx |= RF_FORCE_ENT_ALPHA; //ent->shaderRGBA[3] = 100 + 50*sin(cg.time/200.0); ent->shaderRGBA[3] = 100 + 50*sin(cg.time * 0.005); trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; } //SP Transporter Effect else if ( powerups & ( 1 << PW_BEAM_OUT ) || powerups & ( 1 << PW_QUAD ) ) { int btime; btime = cg.time - beamData->beamTimeParam; if ( btime <= PLAYER_BEAM_FADE ) { if ( powerups & ( 1 << PW_BEAM_OUT ) ) { ent->shaderRGBA[3] = 255; } else { ent->shaderRGBA[3] = 0; } } else if ( btime >= ( PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME ) ) { if ( powerups & ( 1 << PW_BEAM_OUT ) ) { ent->shaderRGBA[3] = 0; } else { ent->shaderRGBA[3] = 255; } } if (btime > PLAYER_BEAM_FADE && btime < (PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME) ) { ent->renderfx |= RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = (int)(255 * beamData->beamAlpha); } if ( ent->shaderRGBA[3] > 0 ) { trap_R_AddRefEntityToScene( ent ); ent->renderfx &= ~RF_FORCE_ENT_ALPHA; ent->shaderRGBA[3] = 255; } if ( btime < 4100 ) { ent->customShader = cgs.media.transportShader; //ent->shaderTime = beamData->beamTimeParam / 1000.0f; ent->shaderTime = beamData->beamTimeParam * 0.001f; trap_R_AddRefEntityToScene( ent ); } } else { trap_R_AddRefEntityToScene( ent ); //if we did have third person alpha if ( ent->renderfx & RF_FORCE_ENT_ALPHA ) { ent->renderfx &= ~RF_FORCE_ENT_ALPHA; } // Quad should JUST be on the weapon now, sparky. /* if ( powerups & ( 1 << PW_QUAD ) ) { if (team == TEAM_RED) ent->customShader = cgs.media.redQuadShader; else ent->customShader = cgs.media.quadShader; trap_R_AddRefEntityToScene( ent ); } */ /*if ( powerups & ( 1 << PW_LASER ) ) { if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { ent->customShader = cgs.media.regenShader; trap_R_AddRefEntityToScene( ent ); } }*/ /*if ( powerups & ( 1 << PW_BEAMING )) { ent->customShader = cgs.media.borgFullBodyShieldShader; trap_R_AddRefEntityToScene( ent ); return; }*/ /* if ( powerups & ( 1 << PW_BOLTON )) { ent->customShader = cgs.media.battleSuitShader; trap_R_AddRefEntityToScene( ent ); return; }*/ /*if (powerups & (1 << PW_OUCH)) { ent->customShader = cgs.media.holoOuchShader; // set rgb to 1 of 16 values from 0 to 255. don't use random so that the three //parts of the player model as well as the gun will all look the same ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = ((cg.time % 17)*0.0625)*255.0;//irandom(0,255); trap_R_AddRefEntityToScene(ent); }*/ if (powerups & (1<customShader = cgs.media.electricBodyShader; // ent->shaderTime = timeParam / 1000.0f; ent->shaderRGBA[0] = ent->shaderRGBA[1] = //ent->shaderRGBA[2] = (int)(1.0 + ((4000.0 - dtime) / 4000.0f * 254.0f )); ent->shaderRGBA[2] = (int)(1.0 + ((4000.0 - dtime) * 0.00025f + 256.0f)); ent->shaderRGBA[3] = 255; trap_R_AddRefEntityToScene( ent ); if ( random() > 0.95f ) { // Play a zap sound to go it. trap_S_StartSound (ent->origin, entNum, CHAN_AUTO, cg_weapons[WP_DERMAL_REGEN].altHitSound); } } } } } #define MAX_SHIELD_TIME 2500.0 #define MIN_SHIELD_TIME 1750.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*20; } 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) + irandom(0, 16); if (alpha>255) alpha=255; // Make it bigger, but tighter if more solid scale = 1.8 - ((float)alpha*(0.4/255.0)); // Range from 1.4 to 1.8 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.explosionModel; 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; } }*/ //------------------------------------ /*void CG_BorgEyebeam( centity_t *cent, const refEntity_t *parent ) { qboolean large = qfalse; vec3_t beamOrg, beamEnd; trace_t trace; refEntity_t temp; CG_PositionEntityOnTag( &temp, parent, parent->hModel, "tag_ear"); if ( VectorCompare( temp.origin, parent->origin )) { // Vectors must be the same so the tag_ear wasn't found return; } //Note the above will also prevent the beam from being drawn in first person view if you don't have a tag_ear // well, we are in thirdperson or whatnot, so we should just render from a bolt-on ( tag_ear ) if ( cent->currentState.clientNum != cg.predictedPlayerState.clientNum || cg.renderingThirdPerson || cg.snap->ps.pm_type == PM_INTERMISSION ) { VectorCopy( temp.origin, beamOrg ); VectorMA( beamOrg, 1024, temp.axis[0], beamEnd );//forward to end } else { vec3_t axis[3]; // stupid offset hack AnglesToAxis( cent->lerpAngles, axis ); VectorMA( cent->lerpOrigin, 26, axis[2], beamOrg );//up VectorMA( beamOrg, -26, axis[1], beamOrg );//right // VectorMA( beamOrg, 0.2f, axis[0], beamOrg );//forward VectorMA( beamOrg, 1024, axis[0], beamEnd );//forward to end large = qtrue; // render a fatter line } trap_CM_BoxTrace( &trace, beamOrg, beamEnd, NULL, NULL, 0, MASK_SHOT ); VectorCopy( trace.endpos, beamEnd ); VectorMA( beamOrg, 0.5, parent->axis[0], beamOrg );//forward FX_BorgEyeBeam( beamOrg, beamEnd, trace.plane.normal, large ); }*/ //------------------------------------------------------------------------------ //Equip mode features void CG_AttachHolsters ( centity_t *cent, refEntity_t *parent, int weapon ) { refEntity_t holster; refEntity_t holsterInner; //CG_FlushRefEnt( parent, &holster ); //CG_FlushRefEnt( parent, &holsterInner ); memset( &holster, 0, sizeof( holster ) ); VectorCopy( parent->lightingOrigin, holster.lightingOrigin ); holster.shadowPlane = parent->shadowPlane; holster.renderfx = parent->renderfx; memset( &holsterInner, 0, sizeof( holsterInner ) ); VectorCopy( parent->lightingOrigin, holsterInner.lightingOrigin ); holsterInner.shadowPlane = parent->shadowPlane; holsterInner.renderfx = parent->renderfx; holster.hModel = (weapon == WP_PHASER) ? cgs.media.phaserHolster : cgs.media.tricorderHolster; holsterInner.hModel = (weapon == WP_PHASER) ? cgs.media.phaserHolsterInner : cgs.media.tricorderHolsterInner; if ( !holster.hModel ) { return; } CG_PositionEntityOnTag( &holster, parent, parent->hModel, (weapon == WP_PHASER) ? "tag_p_holster" : "tag_t_holster" ); CG_PositionEntityOnTag( &holsterInner, &holster, holster.hModel, (weapon == WP_PHASER) ? "tag_phaser" : "tag_tricorder" ); CG_AddRefEntityWithPowerups( &holster, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); if ( cent->currentState.weapon != weapon ) { CG_AddRefEntityWithPowerups( &holsterInner, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); } } void CG_AttachTools ( centity_t *cent, refEntity_t *parent, int weaponNum ) { refEntity_t tool; weaponInfo_t *weaponInfo; weaponInfo = &cg_weapons[weaponNum]; //CG_FlushRefEnt( parent, &holster ); //CG_FlushRefEnt( parent, &holsterInner ); memset( &tool, 0, sizeof( tool ) ); VectorCopy( parent->lightingOrigin, tool.lightingOrigin ); tool.shadowPlane = parent->shadowPlane; tool.renderfx = parent->renderfx; tool.hModel = weaponInfo->weaponModel; if ( !tool.hModel ) { return; } CG_PositionEntityOnTag( &tool, parent, parent->hModel, (weaponNum == WP_MEDKIT) ? "tag_torso" : "tag_lhand" ); if ( cent->currentState.weapon != weaponNum ) { CG_AddRefEntityWithPowerups( &tool, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); } } //-------------------------------------------------------------- //CG version of the flashlight+laser. extern qboolean CG_CalcMuzzlePoint( centity_t *cent, vec3_t muzzle, qboolean isDecoy ); void CG_AddBeamFX( centity_t *cent ) { //refEntity_t ent; trace_t tr; vec3_t origin, forward, end; qboolean isDecoy; if ( !( cent->currentState.powerups & ( 1 << PW_LASER ) ) && !( cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) ) ) { return; } isDecoy = (qboolean)(cent->currentState.eFlags & EF_ITEMPLACEHOLDER ); //Get origin to render effect //============================== //if it's us, we get special treatment, wielding uber-smooth feelings. :) if ( cent->currentState.clientNum == cg.predictedPlayerState.clientNum && !isDecoy ) { VectorCopy( cg.predictedPlayerState.origin, origin ); origin[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL ); } else { //else, we'll employ a rather hacky (but Official Ravensoft made) way to guess the data we'll need here on CG if ( CG_CalcMuzzlePoint( cent, origin, isDecoy ) ) if ( !isDecoy ) AngleVectors( cent->lerpAngles, forward, NULL, NULL ); else AngleVectors( cent->currentState.angles, forward, NULL, NULL ); else return; } //perform the trace VectorMA( origin, 8192, forward, end ); CG_Trace( &tr, origin, NULL, NULL, end, cent->currentState.clientNum, CONTENTS_SOLID ); if (tr.surfaceFlags & SURF_NOIMPACT || tr.surfaceFlags & SURF_SKY ) { return; } //offset back in a little, so we can actually see the ent lol VectorMA( tr.endpos, -0.5, forward, end ); //============================== if ( cent->currentState.powerups & ( 1 << PW_LASER ) && cent->beamData.beamTimeParam == 0 ) { /*// create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( end, ent.origin); ent.reType = RT_SPRITE; //ent.radius = 2; //ent.rotation = 0; ent.data.sprite.radius = 2; ent.customShader = cgs.media.laserShader; trap_R_AddRefEntityToScene( &ent );*/ //TiM: based on the shadow code instead. This is so the sprite //is always aligned to the normal of the surface it hits, making it //act more like a fricken laser. :) CG_ImpactMark( cgs.media.laserShader, end, tr.plane.normal, 0,1,1,1,1, qfalse, 2, qtrue ); } if ( cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) && cent->beamData.beamTimeParam == 0 ) { vec3_t delta; float dist, intensity; //TiM - calc radius based on distance. //So it'll simulate real flashlights getting bigger/smaller the closer u are VectorSubtract( end, origin, delta ); dist = VectorNormalize( delta ); //intensity = ( dist / 256.0f ); intensity = ( dist * 0.00390625f ); if ( intensity > 1.0f ) intensity = 1.0f; if ( intensity < 0.3f ) intensity = 0.3f; trap_R_AddLightToScene( end, intensity * 200.0f, 1, 1, 1); //200 } } //-------------------------------------------------------------- /* =============== CG_Player =============== */ #define EYES_BLINK_TIME 250 void CG_Player( centity_t *cent ) { clientInfo_t *ci; refEntity_t legs; refEntity_t torso; refEntity_t head; int clientNum; int renderfx; qboolean shadow, borg = qfalse; float shadowPlane = 0; //alpha value float alpha = cg_thirdPersonAlpha.value; int i = 0; rankModelData_t* rankModelData = NULL; qboolean rankDataValidated = qfalse; qboolean isDecoy = 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"); } isDecoy = cent->currentState.eFlags & EF_ITEMPLACEHOLDER; //if we're a decoy, use the decoy info instead if ( isDecoy ) ci = &cgs.decoyInfo[ cent->currentState.eventParm ]; else ci = &cgs.clientinfo[ clientNum ]; // it is possible to see corpses from disconnected players that may // not have valid clientinfo if ( !ci->infoValid ) { return; } //borg = ( ci->pClass == PC_BORG ? qtrue : qfalse ); borg = qfalse; // If I'm a borg and there is another borg teleporting, at least allow me to see a trail, then return /*if ( cg.snap->ps.persistant[PERS_CLASS] == PC_BORG && borg && ( cent->currentState.eFlags & EF_NODRAW )) { FX_BorgTeleportTrails( cent->lerpOrigin ); return; }*/ // add the talk baloon or disconnect icon (not in intermission) if ( !cg.intermissionStarted ) { //RPG-X: RedTechie - Make sure there a admin before printing sprites above there head if(cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/){ CG_PlayerSprites( cent ); } } //TiM Little hack for RGBA shader rendering //if we're completely invisible... save us some processing power lol if ( alpha <= 0.0 ) { return; } if (cent->currentState.eFlags & EF_NODRAW) { // Don't draw anymore... return; } memset( &legs, 0, sizeof(legs) ); memset( &torso, 0, sizeof(torso) ); memset( &head, 0, sizeof(head) ); //TiM : Manually refresh anims here to make sure it's updated around the board. //Previously, new players would be loaded, but anim data wouldn't, resulting in really weird looking poses. //Absolutely flushing all current animation data, forcing the code to reset is the only way I managed to get it working O_o if ( !ci->animsFlushed ) { lerpFrame_t *lf; lerpFrame_t *tf; lf = ¢->pe.legs; tf = ¢->pe.torso; lf->animation = tf->animation = NULL; lf->animationNumber = tf->animationNumber = 0; lf->animationTime = tf->animationTime = 0; lf->backlerp = tf->backlerp = 0; lf->frame = tf->frame = 0; lf->frameTime = tf->frameTime = 0; lf->oldFrame = tf->oldFrame = 0; lf->oldFrameTime = tf->oldFrameTime = 0; //TiM : As much as I'd love to do this, it'll delete current angles too, //which results in players abruptly turning sharply when changing models. //And might possibly make corpses do the same... which would look creepy lol //memset( ¢->pe.legs, 0, sizeof ( cent->pe.legs ) ); //memset( ¢->pe.torso, 0, sizeof ( cent->pe.torso ) ); ci->animsFlushed = qtrue; } // get the rotation information CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); // get the animation state (after rotation, to allow feet shuffle) CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, &torso.oldframe, &torso.frame, &torso.backlerp ); // add powerups floating behind the player CG_PlayerPowerups( cent ); //backup catch for the lift entity if ( cg_liftEnts[cent->currentState.clientNum] > 0 && cg_liftEnts[cent->currentState.clientNum] < (cg.time - cgs.levelStartTime) ) cg_liftEnts[cent->currentState.clientNum] = 0; //========================================================== /*CG_CalcBeamAlpha( cent->currentState.powerups, ¢->beamData ); // add the shadow if ( !(cent->currentState.eFlags & EF_ITEMPLACEHOLDER) ) { shadow = CG_PlayerShadow( cent, &shadowPlane, ci->height ); } else { // - unless we are a hologram... shadow=qfalse; shadowPlane=0; }*/ // add a water splash if partially in and out of water //CG_PlayerSplash( cent ); // get the player model information renderfx = 0; if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson && !cg_firstPersonBody.integer && !isDecoy ) { renderfx |= RF_THIRD_PERSON; // only draw in mirrors } /*if ( cg_shadows.integer == 3 && shadow ) { renderfx |= RF_SHADOW_PLANE; }*/ renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all //append this flag if zooming thru the TR-116. //It'll render players over everything else. Not quite as good as the TV 116, but it'll do the job. :) if ( cg.snap->ps.weapon == WP_TR116 && cg.zoomed ) { renderfx |= RF_DEPTHHACK; //This flag makes making haxx for EF SO EASY!!!! O_O!!!!! } // if we've been hit, display proper fullscreen fx //CG_PlayerHitFX(cent); // If we are dead, set up the correct fx. if (cent->currentState.eFlags & EF_DEAD) { if (cent->deathTime==0) cent->deathTime = cg.time; // Set Death Time so you can see yourself if (cent->currentState.time > cg.time) { // Fading out. timestamp=cent->currentState.time; shadow = qfalse; shadowPlane = 0; } else { timestamp = 0; } } else cent->deathTime = 0; // // add the legs // legs.hModel = ci->legsModel; legs.customSkin = ci->legsSkin; VectorCopy( cent->lerpOrigin, legs.origin ); //TiM - A cheap override for some things. //If emoting, offset the player model. Good for solid chairs n stuff //CG_Printf( "Offset = %f\n", ci->modelOffset ); if ( ci->modelOffset != 0 ) { if ( cent->currentState.eFlags & EF_CLAMP_BODY || cent->currentState.eFlags & EF_CLAMP_ALL || cent->currentState.eFlags & EF_ITEMPLACEHOLDER ) { //TiM: A bit of a hacky override. We don't want this on ladder climbing, yet the emote code //now forms the basis of the ladder clamp code if ( (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != BOTH_LADDER_DWN1 && (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != BOTH_LADDER_UP1 && (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != BOTH_LADDER_IDLE ) { vec3_t offset; vec3_t legsYaw; VectorSet( legsYaw, 0, cent->pe.legs.yawAngle, 0 ); AngleVectors( legsYaw, offset, NULL, NULL ); VectorMA( legs.origin, ci->modelOffset, offset, legs.origin ); } } } VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); //legs.shadowPlane = shadowPlane; legs.renderfx = renderfx; legs.nonNormalizedAxes = qtrue; VectorScale( legs.axis[0], ci->height, legs.axis[0]); VectorScale( legs.axis[1], (ci->height * ci->weight), legs.axis[1]); //weight... i think VectorScale( legs.axis[2], ci->height, legs.axis[2]); legs.origin[2] = legs.origin[2] - (24.0f * (1.0f - ci->height)); //Com_Printf( "weight = %f, race = %s\n", ci->weight, ci->race); if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum && cg.renderingThirdPerson && cg_thirdPersonAlpha.value != 1.0 && !isDecoy ) { legs.renderfx |= RF_FORCE_ENT_ALPHA; legs.shaderRGBA[3] = (unsigned char)(alpha * 255.0f); } VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all // Setup the param, in case it is needed. timeParam = cent->deathTime; entNum = cent->currentState.number; //CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, borg ); // if the model failed, allow the default nullmodel to be displayed if (!legs.hModel) { return; } // // add the torso // torso.hModel = ci->torsoModel; if (!torso.hModel) { return; } torso.customSkin = ci->torsoSkin; VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" ); torso.nonNormalizedAxes = qtrue; torso.shadowPlane = shadowPlane; torso.renderfx = renderfx; if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum && cg.renderingThirdPerson && cg_thirdPersonAlpha.value != 1.0 ) { torso.renderfx |= RF_FORCE_ENT_ALPHA; torso.shaderRGBA[3] = (unsigned char)(alpha * 255.0f); } //TiM - If first person rendering, offset the view vector if ( !isDecoy && cent->currentState.clientNum == cg.snap->ps.clientNum && cg_firstPersonBody.integer && !cg.renderingThirdPerson /*&& !( cent->currentState.eFlags & EF_CLAMP_BODY )*/ ) { vec3_t legsYaw; vec3_t offset; if ( cent->currentState.legsAnim != cg.fpsBody.anim ) { refEntity_t ref; vec3_t temp; CG_PositionEntityOnTag( &ref, &torso, torso.hModel, "tag_head"); VectorSubtract( cg.refdef.vieworg, ref.origin, temp ); //make the body smaller if need be //if ( cg.refdef.vieworg[2] < ref.origin[2] ) //{ // cg.fpsBody.sizeOffset = (cg.refdef.vieworg[2] - legs.origin[2]) / (ref.origin[2] - legs.origin[2]); //} //else // cg.fpsBody.sizeOffset = 0.0f; cg.fpsBody.sizeOffset = 0.9f; //normalize, but don't take Z-axis (vertical) into account //cg.fpsBody.offset = 0 + (int)sqrt( (temp[0]*temp[0]) + (temp[1]*temp[1]) ); cg.fpsBody.offset = 2; cg.fpsBody.anim = cent->currentState.legsAnim ; //+15 for good measure :P } if ( cg.fpsBody.offset > 0.0f ) { VectorSet( legsYaw, 0, cent->pe.legs.yawAngle, 0 ); AngleVectors( legsYaw, offset, NULL, NULL ); VectorMA( legs.origin, -cg.fpsBody.offset, offset, legs.origin ); VectorMA( torso.origin, -cg.fpsBody.offset, offset, torso.origin ); } if ( cg.fpsBody.sizeOffset > 0.0f ) { VectorScale( legs.axis[0], cg.fpsBody.sizeOffset, legs.axis[0] ); VectorScale( legs.axis[1], cg.fpsBody.sizeOffset, legs.axis[1] ); VectorScale( legs.axis[2], cg.fpsBody.sizeOffset, legs.axis[2] ); VectorScale( torso.axis[0], cg.fpsBody.sizeOffset, torso.axis[0] ); VectorScale( torso.axis[1], cg.fpsBody.sizeOffset, torso.axis[1] ); VectorScale( torso.axis[2], cg.fpsBody.sizeOffset, torso.axis[2] ); legs.origin[2] = legs.origin[2] - (24.0f * (1.0f - cg.fpsBody.sizeOffset)); torso.origin[2] = torso.origin[2] - (24.0f * (1.0f - cg.fpsBody.sizeOffset)); } //CG_Printf( "Anim: %i Offset: %i\n", cg.fpsBody.anim, cg.fpsBody.offset ); } //CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, borg ); // // add the head // head.hModel = ci->headModel; if (!head.hModel) { return; } head.customSkin = ci->headSkin; if ( !cg_noFrowningHeads.integer && cent->currentState.eFlags & EF_EYES_ANGRY && ci->headSkinFrown ) { head.customSkin = ci->headSkinFrown; } //BLINK CODE //if we have a valid blink skin... //Or we're frowning and we have a frown blink skin if ( (!cg_noBlinkingHeads.integer && ci->headSkinBlink) || (( !cg_noFrowningHeads.integer && ( cent->currentState.eFlags & EF_EYES_ANGRY ) && ci->headSkinFrownBlink) ) ) { //if time exceeded blink time and length of time to allocate blink //Validate that the blink time won't cause errors if ( ( ci->headBlinkTime.minSeconds < ci->headBlinkTime.maxSeconds ) ) { if ( cg.time > ci->headBlinkTime.nextTime ) { //if blink time has been exceeded, assign a new one ci->headBlinkTime.nextTime = ( irandom( ci->headBlinkTime.minSeconds, ci->headBlinkTime.maxSeconds ) * 1000 ) + cg.time; } } //if time has exceeded next blink time, and is still under 250 from then //Or if we've kicked t3h bucket and we're stone dead right now //or if we emoted our eyes shut if ( ( ( cent->currentState.eFlags & EF_EYES_SHUT ) || ( cent->currentState.eFlags & EF_DEAD ) || ( cg.time < ci->headBlinkTime.nextTime && cg.time > ci->headBlinkTime.nextTime - EYES_BLINK_TIME ) ) ) { if ( cent->currentState.eFlags & EF_EYES_ANGRY ) { if ( ci->headSkinFrownBlink ) head.customSkin = ci->headSkinFrownBlink; } else { if ( ci->headSkinBlink ) head.customSkin = ci->headSkinBlink; } } //Com_Printf( "Max: %i, Min: %i, NextTime: %i, Time: %i\n", ci->headBlinkTime.maxSeconds, ci->headBlinkTime.minSeconds, ci->headBlinkTime.nextTime, cg.time ); } //TALK CODE if ( !cg_noTalkingHeads.integer && (cent->currentState.eFlags & EF_TALKING) && ci->headSkinTalk[0] ) { if (cg.time > ci->nextTalkTime ) { while ( i == ci->currentTalkSkin ) { i = irandom( 0, 3 ); } ci->currentTalkSkin = i; ci->nextTalkTime = cg.time + irandom( 100, 150 ); } if ( ci->headSkinTalk[ci->currentTalkSkin] ) head.customSkin = ci->headSkinTalk[ci->currentTalkSkin]; } VectorCopy( cent->lerpOrigin, head.lightingOrigin ); CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head" ); head.nonNormalizedAxes = qtrue; head.shadowPlane = shadowPlane; head.renderfx = renderfx; if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum && cg.renderingThirdPerson && cg_thirdPersonAlpha.value != 1.0 ) { head.renderfx |= RF_FORCE_ENT_ALPHA; head.shaderRGBA[3] = (unsigned char)(alpha * 255.0f); } if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson && cg_firstPersonBody.integer ) head.renderfx |= RF_THIRD_PERSON; //CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, borg ); /*if ( borg && cgs.pModAssimilation ) { CG_BorgEyebeam( cent, &head ); }*/ //player must have just uncloaked. Do the reverse effect if ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) && cent->wasCloaked ) { //play flash VFX if ( cent->decloakTime > 0 ) { if ( cg.time < cent->decloakTime + Q_FLASH_TIME ) { //calc the middle waypoint for the model vec3_t mins, maxs, headTop, avg; trap_R_ModelBounds( head.hModel, mins, maxs ); VectorCopy( head.origin, headTop ); headTop[2] += ( maxs[2] - mins[2] ); VectorCopy( cent->lerpOrigin, mins ); mins[2] -= 12; VectorCopy( headTop, maxs ); VectorAverage( mins, maxs, avg ); if(!ci->silentCloak) { //play the VFX FX_qFlash( cent, avg, cent->decloakTime ); } } else { if ( cent->wasCloaked ) { cent->wasCloaked = qfalse; } } } if ( cent->decloakTime <= 0 ) { cent->decloakTime = cg.time; if(!ci->silentCloak) { trap_S_StartSound ( NULL, cent->currentState.clientNum, CHAN_AUTO, cgs.media.qFlash ); } } } else { if ( cent->decloakTime >= 0 ) { cent->decloakTime = -1; } } //Get the index for when we cloaked. We'll use this to stay visible for a split second after we cloaked to enable to proper Q-Effect //Player just cloaked if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { if ( cent->cloakTime > 0 && cg.time < cent->cloakTime + Q_FLASH_TIME) { //calc the middle waypoint for the model vec3_t mins, maxs, headTop, avg; trap_R_ModelBounds( head.hModel, mins, maxs ); VectorCopy( head.origin, headTop ); headTop[2] += ( maxs[2] - mins[2] ); VectorCopy( cent->lerpOrigin, mins ); mins[2] -= 12; VectorCopy( headTop, maxs ); VectorAverage( mins, maxs, avg ); if(!ci->silentCloak) { //play the VFX FX_qFlash( cent, avg, cent->cloakTime ); } } if ( cent->cloakTime <= 0 ) { cent->wasCloaked = qtrue; cent->cloakTime = cg.time; if(!ci->silentCloak) { trap_S_StartSound ( NULL, cent->currentState.clientNum, CHAN_AUTO, cgs.media.qFlash ); } } } else { if ( cent->cloakTime >= 0 ) cent->cloakTime = -1; } //Like EF_DEAD, but when we're beaming //========================================================= if ( cent->currentState.powerups & ( 1 << PW_BEAM_OUT ) /*&& !( cent->currentState.powerups & ( 1 << PW_INVIS ) )*/ ) { vec3_t mins, maxs, headTop; if (cent->beamData.beamTimeParam == 0 && !cent->beamData.beamInDetected ) { cent->beamData.beamTimeParam = cg.time; CG_AddFullScreenEffect( SCREENFX_SP_TRANSPORTER_OUT, clientNum ); } //Calculate the bounding region of head so beam flares will end there trap_R_ModelBounds( head.hModel, mins, maxs ); VectorCopy( head.origin, headTop ); headTop[2] += ( maxs[2] - mins[2] ); if ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) ) FX_SPTransporterLensFlares( cent, headTop, cent->beamData.beamTimeParam ); } else { if ( !cent->beamData.beamInDetected ) { cent->beamData.beamTimeParam = 0; } } if ( cent->currentState.powerups & ( 1 << PW_QUAD ) && !( cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) { vec3_t mins, maxs, headTop; if ( cent->beamData.beamInDetected == qfalse ) { cent->beamData.beamTimeParam = cg.time; cent->beamData.beamInDetected = qtrue; CG_AddFullScreenEffect( SCREENFX_SP_TRANSPORTER_IN, clientNum ); //normally player ents reset themselves... but we don't to ourselves >.< //so do that now if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) { CG_ResetPlayerEntity( cent ); } } trap_R_ModelBounds( head.hModel, mins, maxs ); VectorCopy( head.origin, headTop ); headTop[2] += ( maxs[2] - mins[2] ); FX_SPTransporterLensFlares( cent, headTop, cent->beamData.beamTimeParam ); } else { if ( cent->beamData.beamInDetected ) { cent->beamData.beamTimeParam = 0; cent->beamData.beamInDetected = qfalse; } } //========================================================= CG_CalcBeamAlpha( cent->currentState.powerups, ¢->beamData ); // add the shadow if ( /*!(cent->currentState.eFlags & EF_ITEMPLACEHOLDER)*/ qtrue ) { shadow = CG_PlayerShadow( cent, legs.origin, &shadowPlane, ci->height ); } else { // - unless we are a hologram... shadow=qfalse; shadowPlane=0; } if ( cg_shadows.integer == 3 && shadow ) { legs.renderfx |= RF_SHADOW_PLANE; torso.renderfx |= RF_SHADOW_PLANE; head.renderfx |= RF_SHADOW_PLANE; } //TiM : Shadow calc must appear after beam code, or else we get intermittent glitches on transitioning legs.shadowPlane = shadowPlane; torso.shadowPlane = shadowPlane; head.shadowPlane = shadowPlane; //TiM - Added here so it's better in the hierarchy CG_PlayerSplash( cent ); CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); //TiM: Add player based interpolated vfx such as flashlight and the fricken laser. :) CG_AddBeamFX( cent ); // // add the gun / barrel / flash // CG_AddPlayerWeapon( &torso, NULL, cent ); //============================================================================= //TR-116 EyeScope!! :) // if ( cent->currentState.weapon == WP_TR116 ) { refEntity_t eyeScope; memset( &eyeScope, 0, sizeof( eyeScope ) ); VectorCopy( cent->lerpOrigin, eyeScope.lightingOrigin ); eyeScope.shadowPlane = shadowPlane; eyeScope.renderfx = renderfx; eyeScope.hModel = cgs.media.tr116EyeScope; if ( !isDecoy && cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) eyeScope.renderfx |= RF_THIRD_PERSON; if ( eyeScope.hModel ) { CG_PositionEntityOnTag( &eyeScope, &head, head.hModel, "tag_eyescope" ); if ( !VectorCompare( eyeScope.origin, head.origin ) ) { CG_AddRefEntityWithPowerups( &eyeScope, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); } } } //SIMS Module if ( cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) ) { refEntity_t flashlight; memset( &flashlight, 0, sizeof( flashlight ) ); VectorCopy( cent->lerpOrigin, flashlight.lightingOrigin ); flashlight.shadowPlane = shadowPlane; flashlight.renderfx = renderfx; flashlight.hModel = cgs.media.simsModule; if ( !isDecoy && cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) flashlight.renderfx |= RF_THIRD_PERSON; if ( flashlight.hModel ) { CG_PositionEntityOnTag( &flashlight, &head, head.hModel, "tag_flashlight" ); if ( !VectorCompare( flashlight.origin, head.origin ) ) { CG_AddRefEntityWithPowerups( &flashlight, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); } } } //medical scanner if ( cgs.classData[ci->pClass].isMedic && cent->currentState.weapon == WP_TRICORDER ) { refEntity_t scanner; memset( &scanner, 0, sizeof( scanner ) ); VectorCopy( cent->lerpOrigin, scanner.lightingOrigin ); scanner.shadowPlane = shadowPlane; scanner.renderfx = renderfx; scanner.hModel = cgs.media.medicalScanner; if ( scanner.hModel ) { CG_PositionEntityOnTag( &scanner, &torso, torso.hModel, "tag_lhand" ); if ( !VectorCompare( scanner.origin, torso.origin ) ) { CG_AddRefEntityWithPowerups( &scanner, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); } } } // //============================================================================= // // attach teh holsters // if ( cent->currentState.powerups & ( 1 << PW_BOLTON ) ) { if ( ci->isHazardModel && !( !cg.renderingThirdPerson && cent->currentState.clientNum == cg.snap->ps.clientNum ) ) { refEntity_t hazardHelmet; memset( &hazardHelmet, 0, sizeof( hazardHelmet ) ); VectorCopy( cent->lerpOrigin, hazardHelmet.lightingOrigin ); hazardHelmet.shadowPlane = shadowPlane; hazardHelmet.renderfx = renderfx; hazardHelmet.hModel = cgs.media.hazardHelmet; if ( cgs.media.hazardHelmet ) { CG_PositionEntityOnTag( &hazardHelmet, &torso, torso.hModel, "tag_head" ); //derive new rotation axis from head model //else the helmet doesn't rotate with the head, wielding funny results AxisCopy( head.axis, hazardHelmet.axis ); if ( !VectorCompare( hazardHelmet.origin, vec3_origin ) ) { CG_AddRefEntityWithPowerups( &hazardHelmet, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); } } } else if ( ci->holsterModel ) { refEntity_t holsterTags; memset( &holsterTags, 0, sizeof(holsterTags) ); VectorCopy( cent->lerpOrigin, holsterTags.lightingOrigin ); holsterTags.shadowPlane = shadowPlane; holsterTags.renderfx = renderfx; holsterTags.hModel = ci->holsterModel; if ( holsterTags.hModel ) { CG_PositionEntityOnTag( &holsterTags, &legs, legs.hModel, "tag_torso" ); trap_R_AddRefEntityToScene( &holsterTags ); //TiM - Added an extra bit of data to entitystate, so now, the game //should transmit each player's weapon list here, so we can make it dynamic now //if player has the phaser, bolt it if ( cent->currentState.time2 & 1 << WP_PHASER ) { CG_AttachHolsters( cent, &holsterTags, WP_PHASER ); } //if player has tricorder if ( cent->currentState.time2 & 1 << WP_TRICORDER ) { CG_AttachHolsters( cent, &holsterTags, WP_TRICORDER ); } //if player has medkit if ( cent->currentState.time2 & 1 << WP_MEDKIT ) { CG_AttachTools( cent, &torso, WP_MEDKIT ); } //if player has toolkit if ( cent->currentState.time2 & 1 << WP_TOOLKIT ) { CG_AttachTools( cent, &torso, WP_TOOLKIT ); } /*switch ( ci->pClass ) { case PC_COMMAND: case PC_ENGINEER: case PC_SECURITY: case PC_SCIENCE: case PC_ADMIN: CG_AttachHolsters( cent, &holsterTags, WP_PHASER ); break; } switch ( ci->pClass ) { case PC_ENGINEER: case PC_SECURITY: case PC_SCIENCE: case PC_MEDICAL: case PC_ADMIN: CG_AttachHolsters( cent, &holsterTags, WP_TRICORDER ); break; } switch ( ci->pClass ) { case PC_ENGINEER: CG_AttachTools( cent, &torso, WP_TOOLKIT ); break; } switch ( ci->pClass ) { case PC_MEDICAL: CG_AttachTools( cent, &torso, WP_MEDKIT ); break; }*/ } } } //============================================================================= // //Custom Boltons //Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[0].modelBase, ci->boltonTags[0].tagName, ci->boltonTags[0].tagModel); //If we have boltons... if ( ci->boltonTags[0].tagModel ) { //We'll test by seeing if a model was cached into the first slot refEntity_t boltOns[MAX_BOLTONS]; refEntity_t* target = NULL; qhandle_t targetModel; for (i = 0; ( ( i < MAX_BOLTONS ) || ( ci->boltonTags[i].tagModel || ci->boltonTags[i].tagName[0] ) ); i++ ) { //if there's no data in there... no point to rendering it //ROFL... uh, we can't use .modelBase since 0 is a valid entry for that one :P if ( !ci->boltonTags[i].tagName[0] || !ci->boltonTags[i].tagModel ) { break; } memset( &boltOns[i], 0, sizeof(boltOns[i]) ); VectorCopy( cent->lerpOrigin, boltOns[i].lightingOrigin ); boltOns[i].renderfx = renderfx; boltOns[i].shadowPlane = shadowPlane; boltOns[i].hModel = ci->boltonTags[i].tagModel; //Get the necessary data for which mesh to bolt to switch ( ci->boltonTags[i].modelBase ){ case BOLTON_HEAD: target = &head; targetModel = ci->headModel; break; case BOLTON_TORSO: target = &torso; targetModel = ci->torsoModel; break; case BOLTON_LEGS: default: target = &legs; targetModel = ci->legsModel; break; } CG_PositionEntityOnTag( &boltOns[i], target, targetModel, ci->boltonTags[i].tagName ); if ( VectorCompare( boltOns[i].origin, target->origin ) ) { break; } CG_AddRefEntityWithPowerups( &boltOns[i], cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); } } //============================================================================= // teh much talked about rank system :) // // //First, check class to determine whether we'll have default or actual rank models displayed if ( !cgs.classData[ci->pClass].showRanks || !ci->hasRanks /*ci->pClass == PC_NOCLASS || ci->pClass == PC_ALIEN || ci->pClass == PC_N00B*/ ) { if ( cgs.defaultRankData.rankModelData.boltModelPath[0] ) { rankModelData = &cgs.defaultRankData.rankModelData; rankDataValidated = qtrue; } } else { //Q_log2 if ( cgs.ranksData[cg.scores[clientNum].score].rankModelData.boltModelPath[0] ) { //Ah... found the error with the ranks displaying wrong. Turns out cg.scores[val] is sorted by score ranking, not clientNum //Gotta manually find ourselves in the list. for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( cg.scores[i].client == clientNum ) { break; } } //rankModelData = &cgs.ranksData[Q_log2( cg.scores[clientNum].score )].rankModelData; rankModelData = &cgs.ranksData[cg.scores[i].score].rankModelData; if ( rankModelData ) rankDataValidated = qtrue; } } if ( !rankDataValidated || cg_noDynamicRanks.integer != 0 ) { return; } //Now check to see if we actually got any data //I've purposely created this deferred kind of rank loading //system to try and minimize the amount of shader slots this thing will use at one time. :P //No point in loading it until we use it if ( rankModelData->boltModelPath[0] ) { //if we didn't try to cache it, try now if ( !rankModelData->boltModel && !rankModelData->triedToLoad ) { rankModelData->boltModel = trap_R_RegisterModel( rankModelData->boltModelPath ); if ( !rankModelData->boltModel ) { Com_Printf( S_COLOR_RED "Unable to load model file: %s\n", rankModelData->boltModelPath ); } if ( rankModelData->boltShaderPath[0] ) { //rankModelData->boltShader = trap_R_RegisterSkin( rankModelData->boltShaderPath ); rankModelData->boltShader = trap_R_RegisterShader( rankModelData->boltShaderPath ); if ( !rankModelData->boltShader ) { Com_Printf( S_COLOR_RED "Unable to load skin: %s\n", rankModelData->boltShaderPath ); } } rankModelData->triedToLoad = qtrue; } } //Now for the actual renderisation of the model if ( rankModelData->boltModel ) { refEntity_t rankPip; memset( &rankPip, 0, sizeof( rankPip ) ); VectorCopy( cent->lerpOrigin, rankPip.lightingOrigin ); rankPip.shadowPlane = shadowPlane; rankPip.renderfx = renderfx; rankPip.hModel = rankModelData->boltModel; if ( rankModelData->boltShader ) { //rankPip.customSkin = rankModelData->boltShader; rankPip.customShader = rankModelData->boltShader; } CG_PositionEntityOnTag( &rankPip, &head, head.hModel, "tag_pip" ); if ( VectorCompare( rankPip.origin, head.origin ) ) { return; } CG_AddRefEntityWithPowerups( &rankPip, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); //for admiral ranks, save the data and re-use, but offset origin+angles if ( rankModelData->admiralRank ) { /*refEntity_t rankPip1; memset( &rankPip1, 0, sizeof( rankPip1 ) ); VectorCopy( cent->lerpOrigin, rankPip1.lightingOrigin ); rankPip.shadowPlane = shadowPlane; rankPip.renderfx = renderfx; rankPip1.hModel = rankModelData->boltModel; if ( rankModelData->boltShader ) { rankPip1.customSkin = rankModelData->boltShader; }*/ //CG_PositionEntityOnTag( &rankPip1, &head, head.hModel, "tag_pip1" ); CG_PositionEntityOnTag( &rankPip, &head, head.hModel, "tag_pip1" ); if ( VectorCompare( rankPip.origin, head.origin ) ) { return; } CG_AddRefEntityWithPowerups( &rankPip, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); } } //TiM - try out the procedural rank quad maker //*sob* no normals = doesn't work :'( //{ // refEntity_t pip; // polyVert_t verts[4]; // //use this to quickly get the tag porsition + orientation // CG_PositionEntityOnTag( &pip, &head, head.hModel, "tag_pip" ); // // if ( VectorCompare( pip.origin, vec3_origin ) || cent->beamData.beamAlpha < 0.5f ) // return; // //lower left // verts[0].modulate[0] = 0xff; // verts[0].modulate[1] = 0xff; // verts[0].modulate[2] = 0xff; // verts[0].modulate[3] = 0xff; // verts[0].st[0] = 0; // verts[0].st[1] = 1; // // //upper left // verts[1].modulate[0] = 0xff; // verts[1].modulate[1] = 0xff; // verts[1].modulate[2] = 0xff; // verts[1].modulate[3] = 0xff; // verts[1].st[0] = 0; // verts[1].st[1] = 0; // //upper right // verts[2].modulate[0] = 0xff; // verts[2].modulate[1] = 0xff; // verts[2].modulate[2] = 0xff; // verts[2].modulate[3] = 0xff; // verts[2].st[0] = 1; // verts[2].st[1] = 0; // //lower right // verts[3].modulate[0] = 0xff; // verts[3].modulate[1] = 0xff; // verts[3].modulate[2] = 0xff; // verts[3].modulate[3] = 0xff; // verts[3].st[0] = 1; // verts[3].st[1] = 1; // //offset each vert per axis // //lower left // VectorMA( pip.origin, -10, pip.axis[1], verts[0].xyz ); //shift the vert right // VectorMA( verts[0].xyz, -5, pip.axis[2], verts[0].xyz ); // //upper left // VectorMA( pip.origin, -10, pip.axis[1], verts[1].xyz ); //shift the vert right // VectorMA( verts[1].xyz, 5, pip.axis[2], verts[1].xyz ); // //upper right // VectorMA( pip.origin, 10, pip.axis[1], verts[2].xyz ); //shift the vert right // VectorMA( verts[2].xyz, 5, pip.axis[2], verts[2].xyz ); // //lower right // VectorMA( pip.origin, 10, pip.axis[1], verts[3].xyz ); //shift the vert right // VectorMA( verts[3].xyz, -5, pip.axis[2], verts[3].xyz ); // trap_R_AddPolyToScene( rankModelData->boltShader, 4, verts ); //} } //===================================================================== /* =============== CG_ResetPlayerEntity A player just came into view or teleported, so reset all animation info =============== */ void CG_ResetPlayerEntity( centity_t *cent ) { qboolean isDecoy=qfalse; isDecoy = (cent->currentState.eFlags & EF_ITEMPLACEHOLDER); cent->errorTime = -99999; // guarantee no error decay added cent->extrapolated = qfalse; if ( !isDecoy ) { CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); } else { CG_ClearLerpFrame( &cgs.decoyInfo[ cent->currentState.eventParm ], ¢->pe.legs, cent->currentState.legsAnim ); CG_ClearLerpFrame( &cgs.decoyInfo[ cent->currentState.eventParm ], ¢->pe.torso, cent->currentState.torsoAnim ); } 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 ); //TiM - Only do this if not emoting. Otherwise, if we go away, then come back, clamped players face different directions O_O //ignore this for decoys too. they're static, so this won't affect them anyway if ( !isDecoy && ( ( cent->currentState.eFlags & EF_CLAMP_BODY) || ( cent->currentState.eFlags & EF_CLAMP_ALL) ) ) { //blah } else { memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); cent->pe.legs.yawAngle = cent->rawAngles[YAW]; cent->pe.legs.yawing = qfalse; cent->pe.legs.pitchAngle = 0; cent->pe.legs.pitching = qfalse; memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); cent->pe.torso.yawAngle = cent->rawAngles[YAW]; cent->pe.torso.yawing = qfalse; cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; cent->pe.torso.pitching = qfalse; } if ( cg_debugPosition.integer ) { CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); } }