/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* ========================================================================== - SPLITMODELS - ========================================================================== */ // - Adding the Player model using Skeletal animation blending // by Jalisk0 #include "cg_local.h" pmodel_t cg_entPModels[MAX_EDICTS]; pmodelinfo_t *cg_PModelInfos; //====================================================================== // PlayerModel Registering //====================================================================== //================ //CG_PModelsInit //================ void CG_PModelsInit( void ) { memset( cg_entPModels, 0, sizeof(cg_entPModels) ); } //================ //CG_CopyAnimation //================ static void CG_CopyAnimation( pmodelinfo_t *pmodelinfo, int put, int pick ) { pmodelinfo->firstframe[put] = pmodelinfo->firstframe[pick]; pmodelinfo->lastframe[put] = pmodelinfo->lastframe[pick]; pmodelinfo->loopingframes[put] = pmodelinfo->loopingframes[pick]; } //================ //CG_FindBoneNum //================ static int CG_FindBoneNum( cgs_skeleton_t *skel, char *bonename ) { int j; if( !skel || !bonename ) return -1; for( j = 0; j < skel->numBones; j++ ) { if( !Q_stricmp(skel->bones[j].name, bonename) ) return j; } return -1; } //================ //CG_ParseRotationBone //================ static void CG_ParseRotationBone( pmodelinfo_t *pmodelinfo, char *token, int pmpart ) { int boneNumber; boneNumber = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), token ); if( boneNumber < 0 ) { if( cg_debugPlayerModels->integer ) CG_Printf( "CG_ParseRotationBone: No such bone name %s\n", token ); return; } //register it into pmodelinfo if( cg_debugPlayerModels->integer ) CG_Printf( "Script: CG_ParseRotationBone: %s is %i\n", token, boneNumber ); pmodelinfo->rotator[pmpart][pmodelinfo->numRotators[pmpart]] = boneNumber; pmodelinfo->numRotators[pmpart]++; } //================ //CG_ParseTagMask //================ static void CG_ParseTagMask( struct model_s *model, int bonenum, char *name, float forward, float right, float up ) { cg_tagmask_t *tagmask; cgs_skeleton_t *skel; if( !name || !name[0] ) return; skel = CG_SkeletonForModel( model ); if( !skel || skel->numBones <= bonenum ) return; //fixme: check the name isn't already in use, or it isn't the same as a bone name //now store it tagmask = CG_Malloc( sizeof(cg_tagmask_t) ); Q_snprintfz( tagmask->tagname, sizeof(tagmask->tagname), name ); Q_snprintfz( tagmask->bonename, sizeof(tagmask->bonename), skel->bones[bonenum].name ); tagmask->bonenum = bonenum; tagmask->offset[0] = forward; tagmask->offset[1] = right; tagmask->offset[2] = up; tagmask->next = skel->tagmasks; skel->tagmasks = tagmask; if( cg_debugPlayerModels->integer ) CG_Printf( "Added Tagmask: %s -> %s\n", tagmask->tagname, tagmask->bonename ); } /* ================ CG_ParseAnimationScript Reads the animation config file. 0 = first frame 1 = number of frames/lastframe 2 = looping frames 3 = frame time Note: The animations count begins at 1, not 0. I preserve zero for "no animation change" --------------- New keywords: nooffset: Uses the first frame value at the script, and no offsets, for the animation. alljumps: Uses 3 different jump animations (bunnyhoping) islastframe: second value of each animation is lastframe instead of numframes Q3 keywords: sex m/f : sets gender Q3 Unsupported: headoffset footsteps ================ */ static qboolean CG_ParseAnimationScript( pmodelinfo_t *pmodelinfo, char *filename ) { qbyte *buf; char *ptr, *token; int rounder, counter, i, offset; qboolean lower_anims_have_offset = qfalse; qboolean alljumps = qfalse; qboolean islastframe = qtrue; qboolean debug = qtrue; int anim_data[3][PMODEL_MAX_ANIMS];//splitskel:data is: firstframe, lastframe, loopingframes int rootanims[PMODEL_PARTS]; int filenum; int length; memset( rootanims, -1, sizeof(rootanims) ); pmodelinfo->sex = GENDER_MALE; pmodelinfo->frametime = 1000/24; //24fps by default rounder = 0; counter = 1; //reseve 0 for 'no animation' if( !cg_debugPlayerModels->integer ) debug = qfalse; // load the file length = trap_FS_FOpenFile( filename, &filenum, FS_READ ); if( length == -1 ) { CG_Printf( "Couldn't find animation script: %s\n", filename ); return qfalse; } buf = CG_Malloc( length + 1 ); length = trap_FS_Read( buf, length, filenum ); trap_FS_FCloseFile( filenum ); if( !length ) { CG_Free( buf ); CG_Printf( "Couldn't load animation script: %s\n", filename ); return qfalse; } //proceed ptr = ( char * )buf; while( ptr ) { token = COM_ParseExt( &ptr, qtrue ); if( !token ) break; if( *token < '0' || *token > '9' ) { //gender if( !Q_stricmp(token, "sex") ) { if( debug ) CG_Printf("Script: %s:", token ); token = COM_ParseExt( &ptr, qfalse ); if( !token ) //Error (fixme) break; if( token[0] == 'm' || token[0] == 'M' ) { pmodelinfo->sex = GENDER_MALE; if( debug ) CG_Printf( " %s -Gender set to MALE\n", token ); } else if( token[0] == 'f' || token[0] == 'F' ) { pmodelinfo->sex = GENDER_FEMALE; if( debug ) CG_Printf( " %s -Gender set to FEMALE\n", token ); } else if( token[0] == 'n' || token[0] == 'N' ) { pmodelinfo->sex = GENDER_NEUTRAL; if( debug ) CG_Printf( " %s -Gender set to NEUTRAL\n", token ); } else { if( debug ) { if(token[0]) CG_Printf (" WARNING: unrecognized token: %s\n", token); else CG_Printf (" WARNING: no value after cmd sex: %s\n", token); } break; //Error } //nooffset } else if( !Q_stricmp(token, "offset") ) { lower_anims_have_offset = qtrue; if( debug ) CG_Printf( "Script: Using offset values for lower frames\n" ); //alljumps } else if( !Q_stricmp(token, "alljumps") ) { alljumps = qtrue; if( debug ) CG_Printf( "Script: Using all jump animations\n" ); //islastframe } else if( !Q_stricmp(token, "isnumframes") ) { islastframe = qfalse; if( debug ) CG_Printf( "Script: Second value is read as numframes\n" ); } else if( !Q_stricmp(token, "islastframe") ) { islastframe = qtrue; if( debug ) CG_Printf( "Script: Second value is read as lastframe\n" ); //FPS } else if( !Q_stricmp(token, "fps") ) { int fps; token = COM_ParseExt ( &ptr, qfalse ); if ( !token ) break; //Error (fixme) fps = (int)atoi(token); if( fps == 0 && debug ) //Error (fixme) CG_Printf( "Script: WARNING:Invalid FPS value (%s) in config for playermodel %s \n", token, filename ); if( fps < 10 ) //never allow less than 10 fps fps = 10; pmodelinfo->frametime = ( 1000/(float)fps ); if( debug ) CG_Printf( "Script: FPS: %i\n", fps ); //Rotation bone } else if( !Q_stricmp(token, "rotationbone") ) { token = COM_ParseExt( &ptr, qfalse ); if( !token || !token[0] ) break; //Error (fixme) if( !Q_stricmp(token, "upper") ) { token = COM_ParseExt( &ptr, qfalse ); if( !token || !token[0] ) break; //Error (fixme) CG_ParseRotationBone( pmodelinfo, token, UPPER ); } else if( !Q_stricmp(token, "head") ) { token = COM_ParseExt( &ptr, qfalse ); if( !token || !token[0] ) break; //Error (fixme) CG_ParseRotationBone( pmodelinfo, token, HEAD ); } else if( debug ) { CG_Printf ("Script: ERROR: Unrecognized rotation pmodel part %s\n", token); CG_Printf ("Script: ERROR: Valid names are: 'upper', 'head'\n"); } //Root animation bone } else if( !Q_stricmp(token, "rootanim") ) { token = COM_ParseExt( &ptr, qfalse ); if( !token || !token[0] ) break; if( !Q_stricmp(token, "upper") ) { rootanims[UPPER] = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), COM_ParseExt( &ptr, qfalse ) ); } else if( !Q_stricmp(token, "head") ) { rootanims[HEAD] = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), COM_ParseExt( &ptr, qfalse ) ); } else if( !Q_stricmp(token, "lower") ) { rootanims[LOWER] = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), COM_ParseExt( &ptr, qfalse ) ); //we parse it so it makes no error, but we ignore it later on CG_Printf ("Script: WARNING: Ignored rootanim lower: Valid names are: 'upper', 'head' (lower is always skeleton root)\n"); } else if( debug ) { CG_Printf ("Script: ERROR: Unrecognized root animation pmodel part %s\n", token); CG_Printf ("Script: ERROR: Valid names are: 'upper', 'head'\n"); } //Tag bone (format is: tagmask "bone name" "tag name") } else if( !Q_stricmp(token, "tagmask") ) { int bonenum; token = COM_ParseExt( &ptr, qfalse ); if( !token || !token[0] ) break;//Error bonenum = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), token ); if( bonenum != -1 ) { char maskname[MAX_QPATH]; float forward, right, up; token = COM_ParseExt( &ptr, qfalse ); if( !token || !token[0] ) { CG_Printf ("Script: ERROR: missing maskname in tagmask for bone %i\n", bonenum ); break;//Error } Q_strncpyz( maskname, token, sizeof(maskname) ); forward = atof( COM_ParseExt( &ptr, qfalse ) ); right = atof( COM_ParseExt( &ptr, qfalse ) ); up = atof( COM_ParseExt( &ptr, qfalse ) ); CG_ParseTagMask( pmodelinfo->model, bonenum, maskname, forward, right, up ); } else if( debug ) { CG_Printf( "Script: WARNING: Unknown bone name: %s\n", token ); } } else if( token[0] && debug ) CG_Printf( "Script: WARNING: unrecognized token: %s\n", token ); } else { //frame & animation values i = (int)atoi(token); if( debug ) CG_Printf( "%i - ", i ); anim_data[rounder][counter] = i; rounder++; if( rounder > 2 ) { rounder = 0; if( debug ) CG_Printf( " anim: %i\n", counter ); counter++; if( counter == PMODEL_MAX_ANIMATIONS ) break; } } } CG_Free(buf); //it must contain at least as many animations as a Q3 script to be valid if( counter-1 < LEGS_TURN ) { CG_Printf( "PModel Error: Not enough animations(%i) at animations script: %s\n", counter, filename ); return qfalse; } //animation ANIM_NONE (0) is always at frame 0, and it's never //received from the game, but just used on the client when none //animation was ever set for a model (head). anim_data[0][ANIM_NONE] = 0; anim_data[1][ANIM_NONE] = 0; anim_data[2][ANIM_NONE] = 1; //reorganize to make my life easier for( i = 0; i < counter; i++ ) { pmodelinfo->firstframe[i] = anim_data[0][i]; if( islastframe ) pmodelinfo->lastframe[i] = anim_data[1][i]; else pmodelinfo->lastframe[i] = ((anim_data[0][i]) + (anim_data[1][i])); pmodelinfo->loopingframes[i] = anim_data[2][i]; } if( lower_anims_have_offset ) { //find offset (Why did they do this???) offset = pmodelinfo->firstframe[LEGS_CRWALK] - pmodelinfo->firstframe[TORSO_GESTURE]; //Remove offset from the lower & extra animations for( i = LEGS_CRWALK; i < counter; ++i ){ pmodelinfo->firstframe[i] -= offset; pmodelinfo->lastframe[i] -= offset; } if( debug ) CG_Printf( "PModel: Fixing offset on lower frames (Q3 format)\n" ); } //Fix the ranges to fit my looping calculations for( i = 0; i < counter; ++i ) { if( pmodelinfo->loopingframes[i] ) pmodelinfo->loopingframes[i] -= 1; if( pmodelinfo->lastframe[i] ) pmodelinfo->lastframe[i] -= 1; } // create a bones array listing the animations each bone will play { cgs_skeleton_t *skel; int j, bonenum; skel = CG_SkeletonForModel( pmodelinfo->model ); rootanims[LOWER] = 0; memset( pmodelinfo->boneAnims, LOWER, sizeof(pmodelinfo->boneAnims) ); for( j = LOWER + 1; j < PMODEL_PARTS; j++ ) { if( rootanims[j] == -1 ) continue; for( i = 0; i < skel->numBones; i++ ) { if( i == rootanims[j] ) { if( pmodelinfo->boneAnims[i] < j ) pmodelinfo->boneAnims[i] = j; continue; } // run up to the tree root searching for rootanim bone bonenum = i; while( skel->bones[bonenum].parent != -1 ) { if( bonenum == rootanims[j] ) { // has the desired bone as parent if( pmodelinfo->boneAnims[i] < j ) pmodelinfo->boneAnims[i] = j; break; } bonenum = skel->bones[bonenum].parent; } } } } //Alljumps: I use 3 jump animations for bunnyhoping //animation support. But they will only be loaded as //bunnyhoping animations if the keyword "alljumps" is //present at the animation.cfg. Otherwise, LEGS_JUMP1 //will be used for all the jump styles. if( !alljumps ) { CG_CopyAnimation( pmodelinfo, LEGS_JUMP3, LEGS_JUMP1 ); CG_CopyAnimation( pmodelinfo, LEGS_JUMP3ST, LEGS_JUMP1ST ); CG_CopyAnimation( pmodelinfo, LEGS_JUMP2, LEGS_JUMP1 ); CG_CopyAnimation( pmodelinfo, LEGS_JUMP2ST, LEGS_JUMP1ST ); } //HACK Uncomplete scripts (for Q3 player models) //---------------------------------------------------- counter--; if( counter < TORSO_RUN ) CG_CopyAnimation( pmodelinfo, TORSO_RUN, TORSO_STAND ); if( counter < TORSO_DROPHOLD ) CG_CopyAnimation( pmodelinfo, TORSO_DROPHOLD, TORSO_STAND ); if( counter < TORSO_DROP ) CG_CopyAnimation( pmodelinfo, TORSO_DROP, TORSO_ATTACK2 ); if( counter < TORSO_PAIN1 ) CG_CopyAnimation( pmodelinfo, TORSO_PAIN1, TORSO_STAND2 ); if( counter < TORSO_PAIN2 ) CG_CopyAnimation( pmodelinfo, TORSO_PAIN2, TORSO_STAND2 ); if( counter < TORSO_PAIN3 ) CG_CopyAnimation( pmodelinfo, TORSO_PAIN3, TORSO_STAND2 ); if( counter < TORSO_SWIM ) CG_CopyAnimation( pmodelinfo, TORSO_SWIM, TORSO_STAND ); if( counter < LEGS_WALKBACK ) CG_CopyAnimation( pmodelinfo, LEGS_WALKBACK, LEGS_RUNBACK ); if( counter < LEGS_WALKLEFT ) CG_CopyAnimation( pmodelinfo, LEGS_WALKLEFT, LEGS_WALKFWD ); if( counter < LEGS_WALKRIGHT ) CG_CopyAnimation( pmodelinfo, LEGS_WALKRIGHT, LEGS_WALKFWD ); if( counter < LEGS_RUNLEFT ) CG_CopyAnimation( pmodelinfo, LEGS_RUNLEFT, LEGS_RUNFWD ); if( counter < LEGS_RUNRIGHT ) CG_CopyAnimation( pmodelinfo, LEGS_RUNRIGHT, LEGS_RUNFWD ); if( counter < LEGS_SWIM ) CG_CopyAnimation( pmodelinfo, LEGS_SWIM, LEGS_SWIMFWD ); return qtrue; } //================ //CG_LoadPModelWeaponSet //These models are also used for the 1st person view attached weapon //================ static void CG_LoadPModelWeaponSet( pmodelinfo_t *pmodelinfo ) { int i; for( i = 0; i < cgs.numWeaponModels; i++ ) { pmodelinfo->weaponIndex[i] = CG_RegisterWeaponModel( cgs.weaponModels[i], i ); if( !cg_vwep->integer ) break; // only do weapon 0 } //special case for weapon 0. Must always load the animation script if( !pmodelinfo->weaponIndex[0] ) pmodelinfo->weaponIndex[0] = CG_CreateWeaponZeroModel( cgs.weaponModels[0] ); } //=============== // CG_PModel_RegisterBoneposes // Sets up skeleton with inline boneposes based on frame/oldframe values //=============== static void CG_PModel_RegisterBoneposes( pmodel_t *pmodel ) { cgs_skeleton_t *skel; if( !pmodel->pmodelinfo->model ) return; skel = CG_SkeletonForModel( pmodel->pmodelinfo->model ); if( skel && skel == pmodel->skel ) return; if( !skel ) // not skeletal model { if( pmodel->skel ) { if( pmodel->curboneposes ) { CG_Free( pmodel->curboneposes ); pmodel->curboneposes = NULL; } if( pmodel->oldboneposes ) { CG_Free( pmodel->oldboneposes ); pmodel->oldboneposes = NULL; } pmodel->skel = NULL; } return; } if( !pmodel->skel || ( pmodel->skel && (pmodel->skel->numBones != skel->numBones) ) || !pmodel->curboneposes || !pmodel->oldboneposes ) { if( pmodel->curboneposes ) CG_Free( pmodel->curboneposes ); if( pmodel->oldboneposes ) CG_Free( pmodel->oldboneposes ); pmodel->curboneposes = CG_Malloc( sizeof(bonepose_t) * skel->numBones ); pmodel->oldboneposes = CG_Malloc( sizeof(bonepose_t) * skel->numBones ); } pmodel->skel = skel; } //================ //CG_LoadPlayerModel //================ static qboolean CG_LoadPlayerModel( pmodelinfo_t *pmodelinfo, char *filename ) { qboolean loaded_model = qfalse; char anim_filename[MAX_QPATH]; char scratch[MAX_QPATH]; Q_snprintfz( scratch, sizeof(scratch), "%s/tris.skm", filename ); pmodelinfo->model = CG_RegisterModel( scratch ); if( !trap_R_SkeletalGetNumBones( pmodelinfo->model, NULL ) ) // pmodels only accept skeletal models return qfalse; //load animations script if( pmodelinfo->model ) { Q_snprintfz( anim_filename, sizeof(anim_filename), "%s/animation.cfg", filename ); loaded_model = CG_ParseAnimationScript( pmodelinfo, anim_filename ); } //clean up if failed if( !loaded_model ) { pmodelinfo->model = NULL; return qfalse; } pmodelinfo->name = CG_CopyString( filename ); // load sexed sounds for this model CG_UpdateSexedSoundsRegistration( pmodelinfo ); CG_LoadPModelWeaponSet( pmodelinfo ); return qtrue; } //=============== //CG_RegisterPModel //PModel is not exactly the model, but the indexes of the //models contained in the pmodel and it's animation data //=============== struct pmodelinfo_s *CG_RegisterPlayerModel( char *filename ) { pmodelinfo_t *pmodelinfo; for( pmodelinfo = cg_PModelInfos; pmodelinfo; pmodelinfo = pmodelinfo->next ) { if( !Q_stricmp( pmodelinfo->name, filename ) ) return pmodelinfo; } pmodelinfo = CG_Malloc( sizeof(pmodelinfo_t) ); if( !CG_LoadPlayerModel( pmodelinfo, filename ) ) { CG_Free( pmodelinfo ); return NULL; } pmodelinfo->next = cg_PModelInfos; cg_PModelInfos = pmodelinfo; return pmodelinfo; } //================ //CG_RegisterBasePModel //Default fallback replacements //================ void CG_RegisterBasePModel( void ) { char filename[MAX_QPATH]; //pmodelinfo Q_snprintfz( filename, sizeof(filename), "%s/%s", "models/players", DEFAULT_PLAYERMODEL ); cgs.basePModelInfo = CG_RegisterPlayerModel( filename ); Q_snprintfz( filename, sizeof(filename), "%s/%s/%s", "models/players" , DEFAULT_PLAYERMODEL, DEFAULT_PLAYERSKIN ); cgs.baseSkin = trap_R_RegisterSkinFile( filename ); if( !cgs.baseSkin ) CG_Error( "'Default Player Model'(%s): Skin (%s) failed to load", DEFAULT_PLAYERMODEL, filename ); if( !cgs.basePModelInfo ) CG_Error( "'Default Player Model'(%s): failed to load", DEFAULT_PLAYERMODEL ); } //====================================================================== // tools //====================================================================== //=============== //CG_GrabTag //In the case of skeletal models, boneposes must //be transformed prior to calling this function //=============== qboolean CG_GrabTag( orientation_t *tag, entity_t *ent, char *tagname ) { cgs_skeleton_t *skel; if( !ent->model ) return qfalse; skel = CG_SkeletonForModel( ent->model ); if( skel ) return CG_SkeletalPoseGetAttachment( tag, skel, ent->boneposes, tagname ); return trap_R_LerpTag( tag, ent->model, ent->frame, ent->oldframe, ent->backlerp, tagname ); } //=============== //CG_PlaceRotatedModelOnTag //=============== void CG_PlaceRotatedModelOnTag( entity_t *ent, entity_t *dest, orientation_t *tag ) { int i; vec3_t tmpAxis[3]; VectorCopy( dest->origin, ent->origin ); VectorCopy( dest->lightingOrigin, ent->lightingOrigin ); for( i = 0 ; i < 3 ; i++ ) VectorMA( ent->origin, tag->origin[i], dest->axis[i], ent->origin ); VectorCopy( ent->origin, ent->oldorigin ); Matrix_Multiply( ent->axis, tag->axis, tmpAxis ); Matrix_Multiply( tmpAxis, dest->axis, ent->axis ); } //=============== //CG_PlaceModelOnTag //=============== void CG_PlaceModelOnTag( entity_t *ent, entity_t *dest, orientation_t *tag ) { int i; VectorCopy( dest->origin, ent->origin ); VectorCopy( dest->lightingOrigin, ent->lightingOrigin ); for( i = 0 ; i < 3 ; i++ ) VectorMA( ent->origin, tag->origin[i], dest->axis[i], ent->origin ); VectorCopy( ent->origin, ent->oldorigin ); Matrix_Multiply( tag->axis, dest->axis, ent->axis ); } //=============== //CG_MoveToTag //"move" tag must have an axis and origin set up. Use vec3_origin and axis_identity for "nothing" //=============== void CG_MoveToTag( vec3_t move_origin, vec3_t move_axis[3], vec3_t dest_origin, vec3_t dest_axis[3], vec3_t tag_origin, vec3_t tag_axis[3] ) { int i; vec3_t tmpAxis[3]; VectorCopy( dest_origin, move_origin ); for( i = 0 ; i < 3 ; i++ ) VectorMA( move_origin, tag_origin[i], dest_axis[i], move_origin ); Matrix_Multiply( move_axis, tag_axis, tmpAxis ); Matrix_Multiply( tmpAxis, dest_axis, move_axis ); } //=============== //CG_PModel_GetProjectionSource //It asumes the player entity is up to date //=============== qboolean CG_PModel_GetProjectionSource( int entnum, orientation_t *tag_result ) { centity_t *cent; pmodel_t *pmodel; if( !tag_result ) return qfalse; if( entnum < 1 || entnum >= MAX_EDICTS ) return qfalse; cent = &cg_entities[entnum]; if( cent->serverFrame != cg.frame.serverFrame ) return qfalse; // see if it's the viewweapon if( vweap.active && (cg.chasedNum+1 == entnum) && !cg.thirdPerson ) { VectorCopy( vweap.projectionSource.origin, tag_result->origin ); Matrix_Copy( vweap.projectionSource.axis, tag_result->axis ); return qtrue; } // it's a 3rd person model pmodel = &cg_entPModels[entnum]; VectorCopy( pmodel->projectionSource.origin, tag_result->origin ); Matrix_Copy( pmodel->projectionSource.axis, tag_result->axis ); return qtrue; } /* // //=============== //CG_PModel_CalcBrassSource //=============== // void CG_PModel_CalcBrassSource( pmodel_t *pmodel, orientation_t *projection ) { orientation_t tag_weapon; orientation_t ref; //the code asumes that the pmodel ents are up-to-date if(!pmodel->pmodelinfo || !pmodel->ent.model || !pmodel->ent.model || !trap_R_LerpTag( &tag_weapon, pmodel->ents[UPPER].model, pmodel->anim.frame[UPPER], pmodel->anim.oldframe[UPPER], pmodel->ent.backlerp, "tag_weapon" ) ) { VectorCopy(pmodel->ents[LOWER].origin, projection->origin); projection->origin[2] += 16; Matrix_Identity(projection->axis); } //brass projection is simply tag_weapon VectorCopy( pmodel->ents[UPPER].origin, ref.origin ); Matrix_Copy( pmodel->ents[UPPER].axis, ref.axis ); CG_MoveToTag ( projection, &ref, &tag_weapon ); //projection->origin[2] += 8; } */ //=============== //CG_AddQuadShell //=============== void CG_AddQuadShell( entity_t *ent ) { entity_t shell; shell = *ent; shell.customSkin = NULL; if( shell.flags & RF_WEAPONMODEL ) shell.customShader = CG_MediaShader( cgs.media.shaderQuadWeapon ); else shell.customShader = CG_MediaShader( cgs.media.shaderPowerupQuad ); shell.flags |= (RF_FULLBRIGHT|RF_NOSHADOW); CG_AddEntityToScene( &shell ); } //=============== //CG_AddPentShell //=============== void CG_AddPentShell( entity_t *ent ) { } //=============== //CG_AddShellEffects //=============== void CG_AddShellEffects( entity_t *ent, int effects ) { // quad and pent can do different things on client if( ent->flags & RF_VIEWERMODEL ) return; if( effects & EF_QUAD ) CG_AddQuadShell(ent); } //=============== //CG_AddColorShell //=============== void CG_AddColorShell( entity_t *ent, int renderfx ) { int i; static entity_t shell; static vec4_t shadelight = { 0.0f, 0.0f, 0.0f, 0.3f }; if( ent->flags & RF_VIEWERMODEL ) return; if( !(renderfx & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE)) ) return; shell = *ent; shell.customSkin = NULL; if( renderfx & RF_SHELL_RED ) shadelight[0] = 1.0; if( renderfx & RF_SHELL_GREEN ) shadelight[1] = 1.0; if( renderfx & RF_SHELL_BLUE ) shadelight[2] = 1.0; for( i = 0; i < 4; i++ ) shell.color[i] = shadelight[i] * 255; if( ent->flags & RF_WEAPONMODEL ) return;//fixme: try the shell shader for viewweapon, or build a good one else shell.customShader = CG_MediaShader( cgs.media.shaderShellEffect ); shell.flags |= (RF_FULLBRIGHT|RF_NOSHADOW); CG_AddEntityToScene( &shell );//vicskmblend } //=============== //CG_OutlineShaderLODfodDist //=============== struct shader_s *CG_OutlineShaderLODForDistance( entity_t *e, float scale ) { float dist; vec3_t dir; // Kill if behind the view or if too far away VectorSubtract( e->origin, cg.refdef.vieworg, dir ); dist = VectorNormalize2( dir, dir ) * cg.view_fracDistFOV; if( dist > 1024 ) return NULL; if( !( e->flags & RF_WEAPONMODEL ) ) { if( DotProduct( dir, cg.v_forward ) < 0 ) return NULL; } dist *= scale; if( dist < 64 || ( e->flags & RF_WEAPONMODEL ) ) { return CG_MediaShader( cgs.media.shaderColoredModelOutlinex1 ); } else if( dist < 128 ) { return CG_MediaShader( cgs.media.shaderColoredModelOutlinex2 ); } else if( dist < 256 ) { return CG_MediaShader( cgs.media.shaderColoredModelOutlinex3 ); } else if( dist < 512 ) { return CG_MediaShader( cgs.media.shaderColoredModelOutlinex4 ); } else { return CG_MediaShader( cgs.media.shaderColoredModelOutlinex5 ); } } //=============== //CG_AddColoredOutLineEffect //=============== void CG_SetOutlineColor( byte_vec4_t outlineColor, vec4_t itemcolor ) { float darken = 0.3f; outlineColor[0] = ( qbyte )( 255 * (itemcolor[0] * darken) ); outlineColor[1] = ( qbyte )( 255 * (itemcolor[1] * darken) ); outlineColor[2] = ( qbyte )( 255 * (itemcolor[2] * darken) ); outlineColor[3] = ( qbyte )( 255 ); } //=============== //CG_AddColoredOutLineEffect //=============== void CG_AddColoredOutLineEffect( entity_t *ent, int effects, qbyte r, qbyte g, qbyte b, qbyte a ) { static entity_t shell; struct shader_s *shader; if( !cg_outlineModels->integer ) return; if( !(effects & EF_OUTLINE) || (ent->flags & RF_VIEWERMODEL) ) return; if( effects & EF_QUAD ) { shader = CG_OutlineShaderLODForDistance( ent, 4.0f ); } else { shader = CG_OutlineShaderLODForDistance( ent, 1.0f ); } if( !shader ) return; shell = *ent; shell.customSkin = NULL; shell.flags = (RF_FULLBRIGHT|RF_NOSHADOW); shell.customShader = shader; if( effects & EF_QUAD ) { shell.color[0] = ( qbyte )( 255 ); shell.color[1] = ( qbyte )( 255 ); shell.color[2] = ( qbyte )( 0 ); shell.color[3] = ( qbyte )( 255 ); } else { shell.color[0] = ( qbyte )( r ); shell.color[1] = ( qbyte )( g ); shell.color[2] = ( qbyte )( b ); shell.color[3] = ( qbyte )( a ); } trap_R_AddEntityToScene( &shell ); } //=============== //CG_AddCentityOutLineEffect //=============== void CG_AddCentityOutLineEffect( centity_t *cent ) { CG_AddColoredOutLineEffect( ¢->ent, cent->effects, cent->outlineColor[0], cent->outlineColor[1], cent->outlineColor[2], cent->outlineColor[3] ); } void CG_AddFlagModelOnTag( centity_t *cent, int flag_team, char *tagname ); //=============== //CG_PModel_AddFlag //=============== void CG_PModel_AddFlag( centity_t *cent ) { int flag_team; flag_team = (cent->current.team == TEAM_RED) ? TEAM_BLUE : TEAM_RED; CG_AddFlagModelOnTag( cent, flag_team, "tag_flag1" ); } //=============== //CG_PModel_AddHeadIcon //=============== void CG_AddHeadIcon( centity_t *cent ) { entity_t balloon; struct shader_s *iconShader = NULL; float radius = 6, upoffset = 8; if( cent->ent.flags & RF_VIEWERMODEL ) return; if( cent->effects & EF_BUSYICON ) { iconShader = CG_MediaShader( cgs.media.shaderChatBalloon ); radius = 12; upoffset = 2; } #ifdef VSAYS else if( cent->localEffects[LOCALEFFECT_VSAY_HEADICON_TIMEOUT] > cg.time ) { if( cent->localEffects[LOCALEFFECT_VSAY_HEADICON] < VSAY_TOTAL ) iconShader = CG_MediaShader( cgs.media.shaderVSayIcon[cent->localEffects[LOCALEFFECT_VSAY_HEADICON]] ); else iconShader = CG_MediaShader( cgs.media.shaderVSayIcon[VSAY_GENERIC] ); radius = 6; upoffset = 4; } #endif // VSAYS // add the current active icon if( iconShader != NULL ) { memset( &balloon, 0, sizeof( entity_t) ); balloon.rtype = RT_SPRITE; balloon.model = NULL; balloon.flags = RF_NOSHADOW; balloon.radius = radius; balloon.customShader = iconShader; balloon.scale = 1.0f; balloon.origin[0] = cent->ent.origin[0]; balloon.origin[1] = cent->ent.origin[1]; balloon.origin[2] = cent->ent.origin[2] + playerbox_stand_maxs[2] + balloon.radius + upoffset; VectorCopy( balloon.origin, balloon.oldorigin ); Matrix_Identity( balloon.axis ); trap_R_AddEntityToScene( &balloon ); } } //====================================================================== // animations //====================================================================== //=============== // CG_PModel_BlendSkeletalPoses // Sets up the boneposes array mixing up bones // from different frames, based on the defined // bone animations. Result bones are not transformed. //=============== static void CG_PModel_BlendSkeletalPoses( cgs_skeleton_t *skel, bonepose_t *boneposes, int *boneAnims, int *boneKeyFrames ) { int i, frame; bonepose_t *framed_bonepose; for( i = 0; i < skel->numBones; i++ ) { frame = boneKeyFrames[ boneAnims[i] ]; if( frame < 0 || frame >= skel->numFrames ) frame = 0; framed_bonepose = skel->bonePoses[frame]; framed_bonepose += i; memcpy( &boneposes[i], framed_bonepose, sizeof(bonepose_t) ); } } /* =============== CG_PModelAnimToFrame Transforms the values inside state->frame into 3 animation values for head, torso and legs (head is always zero right now). It also uses those values to handle the animation frames clientside, as also does the backlerp calculation for variable framerates. The frames and other data is stored inside animationinfo_t. Actually only clientinfo has structs of this type inside, but they could also be applied to a client-side monsterinfo handler. It works like this: -for animations reveived by BASIC_CHANNEL: When a new animation is recieved, the new animation is launched. While 0 is recieved, it advances the frames in last recieved animation. When the frame gets to the end of the animation, it looks for the looping value and sends the frame back to (lastframe - loopingframes) and so on. If the looping value is 0, it holds the animation in the last frame until a new animation is recieved. -for animations reveived by EVENT_CHANNEL: The playing BASIC_CHANNEL animation is put back on the animation buffer, the EVENT_CHANNEL animation is played, and when it comes to it's end,the BASIC_CHANNEL animation at buffer (the same as we put in, or a new one received by that time) is promoted to be played. =============== */ static void CG_PModelAnimToFrame( pmodel_t *pmodel, animationinfo_t *anim ) { int i; pmodelinfo_t *pmodelinfo = pmodel->pmodelinfo; cgs_skeleton_t *skel; //interpolate if( cg.time < anim->nextframetime ) { anim->backlerp = 1.0f - ((cg.time - anim->prevframetime)/(anim->nextframetime - anim->prevframetime)); if (anim->backlerp > 1) anim->backlerp = 1; else if (anim->backlerp < 0) anim->backlerp = 0; return; } for( i=0; ioldframe[i] = anim->frame[i]; anim->frame[i]++; //looping if( anim->frame[i] > pmodelinfo->lastframe[anim->current[i]] ) { if (anim->currentChannel[i]) anim->currentChannel[i] = BASIC_CHANNEL;//kill anim->frame[i] = (pmodelinfo->lastframe[anim->current[i]] - pmodelinfo->loopingframes[anim->current[i]]); } //NEWANIM if( anim->buffer[EVENT_CHANNEL].newanim[i] ) { //backup if basic if (anim->buffer[BASIC_CHANNEL].newanim[i] + anim->currentChannel[i] == BASIC_CHANNEL){//both are zero anim->buffer[BASIC_CHANNEL].newanim[i] = anim->current[i]; } //set up anim->current[i] = anim->buffer[EVENT_CHANNEL].newanim[i]; anim->frame[i] = pmodelinfo->firstframe[anim->current[i]]; anim->currentChannel[i] = EVENT_CHANNEL; anim->buffer[EVENT_CHANNEL].newanim[i] = 0; } else if( anim->buffer[BASIC_CHANNEL].newanim[i] && ( anim->currentChannel[i] != EVENT_CHANNEL ) ) { //set up anim->current[i] = anim->buffer[BASIC_CHANNEL].newanim[i]; anim->frame[i] = pmodelinfo->firstframe[anim->current[i]]; anim->currentChannel[i] = BASIC_CHANNEL; anim->buffer[BASIC_CHANNEL].newanim[i] = 0; } } //new method skel = CG_SkeletonForModel( pmodel->pmodelinfo->model ); if( !skel ) CG_Error( "Non-skeletal PModel inside 'CG_PModelAnimToFrame'\n" ); if( skel != pmodel->skel ) CG_PModel_RegisterBoneposes( pmodel ); // update registration //blend animations into old-current poses CG_PModel_BlendSkeletalPoses( skel, pmodel->curboneposes, pmodel->pmodelinfo->boneAnims, pmodel->anim.frame ); CG_PModel_BlendSkeletalPoses( skel, pmodel->oldboneposes, pmodel->pmodelinfo->boneAnims, pmodel->anim.oldframe ); //updated frametime anim->prevframetime = cg.time; anim->nextframetime = cg.time + pmodelinfo->frametime; anim->backlerp = 1.0f; } //=============== //CG_ClearEventAnimations // Called from CG_PModelUpdateState if the new state has a EV_TELEPORT. // This is done so respawning can reset animations, because event // animations could persist after dying //=============== void CG_ClearEventAnimations( int entNum ) { int i; pmodel_t *pmodel = &cg_entPModels[entNum]; for( i = LOWER ; i < PMODEL_PARTS ; i++ ) { //clean up the buffer pmodel->anim.buffer[EVENT_CHANNEL].newanim[i] = 0; //finish up currents if( pmodel->anim.currentChannel[i] == EVENT_CHANNEL ) { pmodel->anim.frame[i] = pmodel->pmodelinfo->lastframe[pmodel->anim.current[i]]; } pmodel->anim.currentChannel[i] = BASIC_CHANNEL; } } //=============== //CG_AddPModelAnimation //=============== void CG_AddPModelAnimation( int entNum, int loweranim, int upperanim, int headanim, int channel) { int i; int newanim[PMODEL_PARTS]; animationbuffer_t *buffer; pmodel_t *pmodel = &cg_entPModels[entNum]; newanim[LOWER] = loweranim; newanim[UPPER] = upperanim; newanim[HEAD] = headanim; buffer = &pmodel->anim.buffer[channel]; for( i = 0 ; i < PMODEL_PARTS ; i++ ) { //ignore new events if in death if( channel && buffer->newanim[i] && (buffer->newanim[i] < TORSO_GESTURE) ) continue; if( newanim[i] && (newanim[i] < PMODEL_MAX_ANIMS) ) buffer->newanim[i] = newanim[i]; } } //============== //CG_PModels_AddFireEffects //start fire animations, flashes, etc //============== void CG_PModels_AddFireEffects( entity_state_t *state ) { if( state->effects & EF_CORPSE ) return; //if it's the user in 1st person, viewapon code handles this if( state->number == cg.chasedNum+1 && !cg.thirdPerson ) return; //activate weapon flash if( cg_weaponFlashes->integer ) { pmodel_t *pmodel = &cg_entPModels[state->number]; pmodel->pweapon.flashtime = cg.time + (int)pmodel->pmodelinfo->frametime; } //add effects switch( state->weapon ) { case WEAP_GUNBLADE: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; case WEAP_SHOCKWAVE: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; case WEAP_ROCKETLAUNCHER: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; case WEAP_GRENADELAUNCHER: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; case WEAP_PLASMAGUN: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; case WEAP_RIOTGUN: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; case WEAP_ELECTROBOLT: CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL ); break; default: break; } } //====================================================================== // player model //====================================================================== //================== //CG_PModelFixOldAnimationMiss //Run the animtoframe twice so it generates an old skeleton pose //================== static void CG_PModelFixOldAnimationMiss( int entNum ) { pmodel_t *pmodel = &cg_entPModels[entNum]; //set up old frame pmodel->anim.nextframetime = cg.time; CG_PModelAnimToFrame( pmodel, &pmodel->anim ); } int CG_ForceTeam( int entNum, int team ) { static int forceEnemyTeam = 0; static int forceMyTeam = 0; if( cg_forceEnemyTeam->modified ) { if( !cg_forceEnemyTeam->string || !strlen(cg_forceEnemyTeam->string) ) { forceEnemyTeam = 0; } else { forceEnemyTeam = GS_Teams_TeamFromName( cg_forceEnemyTeam->string ); if( forceEnemyTeam <= 0 || forceEnemyTeam >= GS_MAX_TEAMS ) { CG_Printf( "%sWarning: User tried to force an invalid team%s\n", S_COLOR_YELLOW, S_COLOR_WHITE ); trap_Cvar_Set( "cg_forceEnemyTeam", "" ); forceEnemyTeam = 0; } } cg_forceEnemyTeam->modified = qfalse; } if( cg_forceMyTeam->modified ) { if( !cg_forceMyTeam->string || !strlen(cg_forceMyTeam->string) ) { forceMyTeam = 0; } else { forceMyTeam = GS_Teams_TeamFromName( cg_forceMyTeam->string ); if( forceMyTeam <= 0 || forceMyTeam >= GS_MAX_TEAMS ) { CG_Printf( "%sWarning: User tried to force an invalid team%s\n", S_COLOR_YELLOW, S_COLOR_WHITE ); trap_Cvar_Set( "cg_forceMyTeam", "" ); forceMyTeam = 0; } } cg_forceMyTeam->modified = qfalse; } if( forceEnemyTeam ) { if( !GS_Gametype_IsTeamBased(cg.frame.playerState.stats[STAT_GAMETYPE]) ) { if( entNum != cg.chasedNum+1 ) return forceEnemyTeam; } else { if( team != cg.frame.playerState.stats[STAT_TEAM] ) return forceEnemyTeam; } } if( forceMyTeam ) { if( !GS_Gametype_IsTeamBased(cg.frame.playerState.stats[STAT_GAMETYPE]) ) { if( entNum == cg.chasedNum+1 ) return forceMyTeam; } else { if( team == cg.frame.playerState.stats[STAT_TEAM] ) return forceMyTeam; } } return team; } void CG_RegisterForceModel( cvar_t *teamForceModel, cvar_t *teamForceSkin, pmodelinfo_t **ppmodelinfo, struct skinfile_s **pskin ) { pmodelinfo_t *pmodelinfo; struct skinfile_s *skin = NULL; if( teamForceModel ) teamForceModel->modified = qfalse; if( teamForceSkin ) teamForceSkin->modified = qfalse; if( !ppmodelinfo || !pskin ) return; *ppmodelinfo = NULL; // disabled force models *pskin = NULL; // register new ones if possible if( teamForceModel->string[0] ) { pmodelinfo = CG_RegisterPlayerModel( va( "models/players/%s", teamForceModel->string ) ); // if it failed, it will be NULL, so also disabled if( pmodelinfo ) { // when we register a new model, we must re-register the skin, even if the cvar is not modified skin = trap_R_RegisterSkinFile( va( "models/players/%s/%s", teamForceModel->string, teamForceSkin->string ) ); // if the skin failed, we can still try with default value (so only setting model cvar has a visible effect) if( !skin ) { skin = trap_R_RegisterSkinFile( va( "models/players/%s/%s", teamForceModel->string, teamForceSkin->dvalue ) ); } } else { teamForceModel->string[0] = 0; } if( pmodelinfo && skin ) { *ppmodelinfo = pmodelinfo; *pskin = skin; } } } // initialize all void CG_RegisterForceModels( void ) { CG_RegisterForceModel( cg_teamPLAYERSmodel, cg_teamPLAYERSskin, &cgs.teamPLAYERSModelInfo, &cgs.teamPLAYERSCustomSkin ); CG_RegisterForceModel( cg_teamREDmodel, cg_teamREDskin, &cgs.teamREDModelInfo, &cgs.teamREDCustomSkin ); CG_RegisterForceModel( cg_teamBLUEmodel, cg_teamBLUEskin, &cgs.teamBLUEModelInfo, &cgs.teamBLUECustomSkin ); CG_RegisterForceModel( cg_teamGREENmodel, cg_teamGREENskin, &cgs.teamGREENModelInfo, &cgs.teamGREENCustomSkin ); CG_RegisterForceModel( cg_teamYELLOWmodel, cg_teamYELLOWskin, &cgs.teamYELLOWModelInfo, &cgs.teamYELLOWCustomSkin ); } pmodelinfo_t *CG_PModelForCentity( centity_t *cent ) { int team; centity_t *owner; owner = cent; if( cent->effects & EF_CORPSE && cent->current.bodyOwner ) // it's a body owner = &cg_entities[cent->current.bodyOwner]; team = CG_ForceTeam( owner->current.number, owner->current.team ); // get the team model. switch( team ) { case TEAM_RED: { if( cg_teamREDmodel->modified || cg_teamREDskin->modified ) { CG_RegisterForceModel( cg_teamREDmodel, cg_teamREDskin, &cgs.teamREDModelInfo, &cgs.teamREDCustomSkin ); } if( cgs.teamREDModelInfo ) { // There is a force model for this team return cgs.teamREDModelInfo; } } break; case TEAM_BLUE: { if( cg_teamBLUEmodel->modified || cg_teamBLUEskin->modified ) { CG_RegisterForceModel( cg_teamBLUEmodel, cg_teamBLUEskin, &cgs.teamBLUEModelInfo, &cgs.teamBLUECustomSkin ); } if( cgs.teamBLUEModelInfo ) { // There is a force model for this team return cgs.teamBLUEModelInfo; } } break; case TEAM_GREEN: { if( cg_teamGREENmodel->modified || cg_teamGREENskin->modified ) { CG_RegisterForceModel( cg_teamGREENmodel, cg_teamGREENskin, &cgs.teamGREENModelInfo, &cgs.teamGREENCustomSkin ); } if( cgs.teamGREENModelInfo ) { // There is a force model for this team return cgs.teamGREENModelInfo; } } break; case TEAM_YELLOW: { if( cg_teamYELLOWmodel->modified || cg_teamYELLOWskin->modified ) { CG_RegisterForceModel( cg_teamYELLOWmodel, cg_teamYELLOWskin, &cgs.teamYELLOWModelInfo, &cgs.teamYELLOWCustomSkin ); } if( cgs.teamYELLOWModelInfo ) { // There is a force model for this team return cgs.teamYELLOWModelInfo; } } break; case TEAM_PLAYERS: default: // spectators will be this { if( cg_teamPLAYERSmodel->modified || cg_teamPLAYERSskin->modified ) { CG_RegisterForceModel( cg_teamPLAYERSmodel, cg_teamPLAYERSskin, &cgs.teamPLAYERSModelInfo, &cgs.teamPLAYERSCustomSkin ); } if( cgs.teamPLAYERSModelInfo ) { // There is a force model for this team return cgs.teamPLAYERSModelInfo; } } break; } // return player defined one return cgs.pModelsIndex[cent->current.modelindex]; } struct skinfile_s *CG_SkinForCentity( centity_t *cent ) { int team; centity_t *owner; owner = cent; if( cent->effects & EF_CORPSE && cent->current.bodyOwner ) // it's a body owner = &cg_entities[cent->current.bodyOwner]; team = CG_ForceTeam( owner->current.number, owner->current.team ); // get the team model. switch( team ) { case TEAM_RED: { if( cgs.teamREDCustomSkin ) { // There is a force model for this team return cgs.teamREDCustomSkin; } } break; case TEAM_BLUE: { if( cgs.teamBLUECustomSkin ) { // There is a force model for this team return cgs.teamBLUECustomSkin; } } break; case TEAM_GREEN: { if( cgs.teamGREENCustomSkin ) { // There is a force model for this team return cgs.teamGREENCustomSkin; } } break; case TEAM_YELLOW: { if( cgs.teamYELLOWCustomSkin ) { // There is a force model for this team return cgs.teamYELLOWCustomSkin; } } break; case TEAM_PLAYERS: default: // specators will be this { if( cgs.teamPLAYERSCustomSkin ) { // There is a force model for this team return cgs.teamPLAYERSCustomSkin; } } break; } // return player defined one return cgs.skinPrecache[cent->current.skinnum]; } void CG_SetPlayerColor( centity_t *cent ) { int team; centity_t *owner; vec4_t tempcolor; cvar_t *teamForceColor = NULL; int rgbcolor; int *forceColor; owner = cent; if( cent->effects & EF_CORPSE && cent->current.bodyOwner ) // it's a body owner = &cg_entities[cent->current.bodyOwner]; team = CG_ForceTeam( owner->current.number, owner->current.team ); switch( team ) { case TEAM_RED: { teamForceColor = cg_teamREDcolor; forceColor = &cgs.teamREDColor; } break; case TEAM_BLUE: { teamForceColor = cg_teamBLUEcolor; forceColor = &cgs.teamBLUEColor; } break; case TEAM_GREEN: { teamForceColor = cg_teamGREENcolor; forceColor = &cgs.teamGREENColor; } break; case TEAM_YELLOW: { teamForceColor = cg_teamYELLOWcolor; forceColor = &cgs.teamYELLOWColor; } break; case TEAM_PLAYERS: default: { teamForceColor = cg_teamPLAYERScolor; forceColor = &cgs.teamPLAYERSColor; } break; } if( teamForceColor->modified ) { // load default one if in team based gametype if( team >= TEAM_RED ) { rgbcolor = COM_ReadColorRGBString( teamForceColor->dvalue ); if( rgbcolor != -1 ) { // won't be -1 unless some coder defines a weird cvar *forceColor = rgbcolor; } } // if there is a force color, update with it if( teamForceColor->string[0] ) { rgbcolor = COM_ReadColorRGBString( teamForceColor->string ); if( rgbcolor != -1 ) { *forceColor = rgbcolor; } else { teamForceColor->string[0] = 0; // didn't work, disable force color } } teamForceColor->modified = qfalse; } //if forcemodels is enabled or it is color forced team we do, if( teamForceColor->string[0] || team >= TEAM_RED ) { // skin color to team color rgbcolor = *forceColor; Vector4Set( cent->color, COLOR_R(rgbcolor), COLOR_G(rgbcolor), COLOR_B(rgbcolor), 255 ); } else { // user defined colors // see if this player model has a client for using his custom color if( owner->current.number - 1 < MAX_CLIENTS ) { Vector4Copy( cgs.clientInfo[ owner->current.number - 1 ].color, cent->color ); } else { Vector4Set( cent->color, 255, 255, 255, 255 ); } } //outlines if( cg_outlinePlayersBlack->integer ) { Vector4Set( cent->outlineColor, 0, 0, 0, 255 ); } else { Vector4Scale( cent->color, 1.0f/255.0f, tempcolor ); CG_SetOutlineColor( cent->outlineColor, tempcolor ); } } //================== //CG_UpdatePlayerModelEnt // Called each new serverframe //================== void CG_UpdatePlayerModelEnt( centity_t *cent ) { int newanim[PMODEL_PARTS]; int i; pmodel_t *pmodel; // start from clean memset( ¢->ent, 0, sizeof( cent->ent ) ); cent->ent.scale = 1.0f; cent->ent.rtype = RT_MODEL; Vector4Set( cent->ent.color, 255, 255, 255, 255 ); pmodel = &cg_entPModels[cent->current.number]; pmodel->pmodelinfo = CG_PModelForCentity( cent ); pmodel->skin = CG_SkinForCentity( cent ); CG_SetPlayerColor( cent ); //fallback if( !pmodel->pmodelinfo || !pmodel->skin ) { pmodel->pmodelinfo = cgs.basePModelInfo; pmodel->skin = cgs.baseSkin; } // make sure al poses have their memory space CG_RegisterBoneposesForCGEntity( cent, pmodel->pmodelinfo->model ); CG_PModel_RegisterBoneposes( pmodel ); cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes; //Update pweapon pmodel->pweapon.weaponInfo = CG_GetWeaponFromPModelIndex( pmodel, cent->current.weapon ); //update parts rotation angles for( i = LOWER ; i < PMODEL_PARTS ; i++ ) VectorCopy( pmodel->angles[i], pmodel->oldangles[i] ); cent->effects |= EF_OUTLINE; // add always EF_OUTLINE to players. if( !(cent->effects & EF_CORPSE) ) { //lower has horizontal direction, and zeroes vertical pmodel->angles[LOWER][PITCH] = 0; pmodel->angles[LOWER][YAW] = cent->current.angles[YAW]; pmodel->angles[LOWER][ROLL] = 0; //upper marks vertical direction (total angle, so it fits aim) if( cent->current.angles[PITCH] > 180 ) pmodel->angles[UPPER][PITCH] = (-360 + cent->current.angles[PITCH]); else pmodel->angles[UPPER][PITCH] = cent->current.angles[PITCH]; pmodel->angles[UPPER][YAW] = 0; pmodel->angles[UPPER][ROLL] = 0; //head adds a fraction of vertical angle again if( cent->current.angles[PITCH] > 180 ) pmodel->angles[HEAD][PITCH] = (-360 + cent->current.angles[PITCH])/3; else pmodel->angles[HEAD][PITCH] = cent->current.angles[PITCH]/3; pmodel->angles[HEAD][YAW] = 0; pmodel->angles[HEAD][ROLL] = 0; } // copy, not interpolated, starting positions (oldframe ones) cent->ent.backlerp = 1.0f; VectorCopy( cent->prev.origin, cent->ent.origin ); VectorCopy( cent->prev.origin, cent->ent.oldorigin ); VectorCopy( cent->prev.origin, cent->ent.lightingOrigin ); if( !(cent->effects & EF_CORPSE) ) AnglesToAxis( pmodel->oldangles[LOWER], cent->ent.axis ); else AnglesToAxis( cent->prev.angles, cent->ent.axis ); //Spawning (EV_TELEPORT) forces nobacklerp and the interruption of EVENT_CHANNEL animations if( (cent->current.events[0] == EV_TELEPORT) || (cent->current.events[1] == EV_TELEPORT) ) { CG_ClearEventAnimations( cent->current.number ); CG_AddPModelAnimation( cent->current.number, (cent->current.frame)&0x3F, (cent->current.frame>>6)&0x3F, (cent->current.frame>>12)&0xF, BASIC_CHANNEL ); CG_PModelFixOldAnimationMiss( cent->current.number ); for( i = LOWER ; i < PMODEL_PARTS ; i++ ) VectorCopy( pmodel->angles[i], pmodel->oldangles[i] ); return; } //filter repeated animations coming from state->frame newanim[LOWER] = (cent->current.frame&0x3F) * ((cent->current.frame &0x3F) != (cent->prev.frame &0x3F)); newanim[UPPER] = (cent->current.frame>>6 &0x3F) * ((cent->current.frame>>6 &0x3F) != (cent->prev.frame>>6 &0x3F)); newanim[HEAD] = (cent->current.frame>>12 &0xF) * ((cent->current.frame>>12 &0xF) != (cent->prev.frame>>12 &0xF)); CG_AddPModelAnimation( cent->current.number, newanim[LOWER], newanim[UPPER], newanim[HEAD], BASIC_CHANNEL); } #ifdef _DEBUG //#define CRAP_DEBUG_BOX // private test (shows player model bbox) #endif void CG_PModel_SpawnTeleportEffect( centity_t *cent ); void CG_AddLinearTrail( centity_t *cent, float lifetime ); //=============== //CG_AddPModelEnt //=============== void CG_AddPModel( centity_t *cent ) { int i, j; pmodel_t *pmodel; vec3_t tmpangles; orientation_t tag_weapon; pmodel = &cg_entPModels[cent->current.number]; // if viewer model, and casting shadows, offset the entity to predicted player position // for view and shadow accurary if( cent->ent.flags & RF_VIEWERMODEL && !(cent->renderfx & RF_NOSHADOW) ) { vec3_t org; if( cg_predict->integer && !(cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION) ) { vec3_t angles; float backlerp = 1.0f - cg.lerpfrac; int timeDelta; for( i = 0; i < 3; i++ ) org[i] = cg.predictedOrigin[i] - backlerp * cg.predictionError[i]; // smooth out stair climbing timeDelta = cg.realTime - cg.predictedStepTime; if ( timeDelta < PREDICTED_STEP_TIME ) { org[2] -= cg.predictedStep * (PREDICTED_STEP_TIME - timeDelta) / PREDICTED_STEP_TIME; } // use refdef view angles on the model angles[YAW] = cg.refdef.viewangles[YAW]; angles[PITCH] = 0; angles[ROLL] = 0; AnglesToAxis( angles, cent->ent.axis ); } else VectorCopy( cent->ent.origin, org ); // offset it some units back VectorMA( org, -24, cent->ent.axis[0], org ); VectorCopy( org, cent->ent.origin); VectorCopy( org, cent->ent.oldorigin ); VectorCopy( org, cent->ent.lightingOrigin ); } // transform animation values into frames, and set up old-current poses pair CG_PModelAnimToFrame( pmodel, &pmodel->anim ); // lerp old-current poses pair into interpolated pose CG_LerpBoneposes( pmodel->skel, pmodel->curboneposes, pmodel->oldboneposes, centBoneposes[cent->current.number].lerpboneposes, 1.0f - pmodel->anim.backlerp ); // relink interpolated pose into ent cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes; // add skeleton effects (pose is unmounted yet) if( !(cent->effects & EF_CORPSE) ) { // apply interpolated LOWER angles to entity for( j = 0; j < 3; j++ ) tmpangles[j] = LerpAngle( pmodel->oldangles[LOWER][j], pmodel->angles[LOWER][j], cg.lerpfrac ); AnglesToAxis( tmpangles, cent->ent.axis ); // apply UPPER and HEAD angles to rotator bones for( i=1; ipmodelinfo->numRotators[i] ) { // lerp rotation and divide angles by the number of rotation bones for( j = 0; j < 3; j++ ){ tmpangles[j] = LerpAngle( pmodel->oldangles[i][j], pmodel->angles[i][j], cg.lerpfrac ); tmpangles[j] /= pmodel->pmodelinfo->numRotators[i]; } for( j=0; jpmodelinfo->numRotators[i]; j++ ) CG_RotateBonePose( tmpangles, ¢->ent.boneposes[pmodel->pmodelinfo->rotator[i][j]] ); } } } // finish (mount) pose. Now it's the final skeleton just as it's drawn. CG_TransformBoneposes( centBoneposes[cent->current.number].skel, centBoneposes[cent->current.number].lerpboneposes, centBoneposes[cent->current.number].lerpboneposes); cent->ent.backlerp = 0.0f; cent->ent.frame = cent->ent.oldframe = 0;//frame fields are not used with external poses // Add playermodel ent cent->ent.scale = 1.0f; cent->ent.rtype = RT_MODEL; cent->ent.customShader = NULL; cent->ent.model = pmodel->pmodelinfo->model; cent->ent.customSkin = pmodel->skin; Vector4Copy( cent->color, cent->ent.color ); CG_AddEntityToScene( ¢->ent ); if( !cent->ent.model ) return; CG_PModel_AddFlag( cent ); CG_AddCentityOutLineEffect( cent ); CG_AddShellEffects( ¢->ent, cent->effects ); CG_AddColorShell( ¢->ent, cent->renderfx ); CG_AddHeadIcon( cent ); if( cg_showPlayerTrails->value ) CG_AddLinearTrail( cent, cg_showPlayerTrails->value ); #ifdef CGAMEGETLIGHTORIGIN if( !(cent->ent.flags & RF_NOSHADOW) ) CG_AllocShadeBox( cent->current.number, cent->ent.origin, playerbox_stand_mins, playerbox_stand_maxs, NULL ); #endif // add teleporter sfx if needed CG_PModel_SpawnTeleportEffect( cent ); #ifdef CRAP_DEBUG_BOX //jal private test if(!(cent->effects & EF_CORPSE)) CG_DrawTestBox( cent->ent.origin, playerbox_stand_mins, playerbox_stand_maxs ); #endif // add weapon model if( cent->current.weapon && CG_GrabTag( &tag_weapon, ¢->ent, "tag_weapon" ) ) CG_AddWeaponOnTag( ¢->ent, &tag_weapon, &pmodel->pweapon, cent->effects | EF_OUTLINE, &pmodel->projectionSource ); }