/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code 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 3 of the License, or (at your option) any later version. Wolf ET Source Code 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /* ** Character loading */ #include "cg_local.h" char bigTextBuffer[100000]; // TTimo - defined but not used #if 0 /* ====================== CG_ParseGibModels Read a configuration file containing gib models for use with this character ====================== */ static qboolean CG_ParseGibModels( bg_playerclass_t* classInfo ) { char *text_p; int len; int i; char *token; fileHandle_t f; memset( classInfo->gibModels, 0, sizeof( classInfo->gibModels ) ); // load the file len = trap_FS_FOpenFile( va( "models/players/%s/gibs.cfg", classInfo->modelPath ), &f, FS_READ ); if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( bigTextBuffer ) - 1 ) { CG_Printf( "File %s too long\n", va( "models/players/%s/gibs.cfg", classInfo->modelPath ) ); return qfalse; } trap_FS_Read( bigTextBuffer, len, f ); bigTextBuffer[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = bigTextBuffer; for ( i = 0; i < MAX_GIB_MODELS; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } // cache this model classInfo->gibModels[i] = trap_R_RegisterModel( token ); } return qtrue; } #endif /* ====================== CG_ParseHudHeadConfig ====================== */ static qboolean CG_ParseHudHeadConfig( const char *filename, animation_t* hha ) { char *text_p; int len; int i; float fps; char *token; fileHandle_t f; // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( bigTextBuffer ) - 1 ) { CG_Printf( "File %s too long\n", filename ); return qfalse; } trap_FS_Read( bigTextBuffer, len, f ); bigTextBuffer[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = bigTextBuffer; for ( i = 0 ; i < MAX_HD_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); // first frame if ( !token ) { break; } hha[i].firstFrame = atoi( token ); token = COM_Parse( &text_p ); // length if ( !token ) { break; } hha[i].numFrames = atoi( token ); token = COM_Parse( &text_p ); // fps if ( !token ) { break; } fps = atof( token ); if ( fps == 0 ) { fps = 1; } hha[i].frameLerp = 1000 / fps; hha[i].initialLerp = 1000 / fps; token = COM_Parse( &text_p ); // looping frames if ( !token ) { break; } hha[i].loopFrames = atoi( token ); if ( hha[i].loopFrames > hha[i].numFrames ) { hha[i].loopFrames = hha[i].numFrames; } else if ( hha[i].loopFrames < 0 ) { hha[i].loopFrames = 0; } } if ( i != MAX_HD_ANIMATIONS ) { CG_Printf( "Error parsing hud head animation file: %s", filename ); return qfalse; } return qtrue; } /* ================== CG_CalcMoveSpeeds ================== */ static void CG_CalcMoveSpeeds( bg_character_t *character ) { char *tags[2] = {"tag_footleft", "tag_footright"}; vec3_t oldPos[2]; refEntity_t refent; animation_t *anim; int i, j, k; float totalSpeed; int numSpeed; int lastLow, low; orientation_t o[2]; memset( &refent, 0, sizeof( refent ) ); refent.hModel = character->mesh; for ( i = 0; i < character->animModelInfo->numAnimations; i++ ) { anim = character->animModelInfo->animations[i]; if ( anim->moveSpeed >= 0 ) { continue; } totalSpeed = 0; lastLow = -1; numSpeed = 0; // for each frame for ( j = 0; j < anim->numFrames; j++ ) { refent.frame = anim->firstFrame + j; refent.oldframe = refent.frame; refent.torsoFrameModel = refent.oldTorsoFrameModel = refent.frameModel = refent.oldframeModel = anim->mdxFile; // for each foot for ( k = 0; k < 2; k++ ) { if ( trap_R_LerpTag( &o[k], &refent, tags[k], 0 ) < 0 ) { CG_Error( "CG_CalcMoveSpeeds: unable to find tag %s, cannot calculate movespeed", tags[k] ); } } // find the contact foot if ( anim->flags & ANIMFL_LADDERANIM ) { if ( o[0].origin[0] > o[1].origin[0] ) { low = 0; } else { low = 1; } totalSpeed += fabs( oldPos[low][2] - o[low].origin[2] ); } else { if ( o[0].origin[2] < o[1].origin[2] ) { low = 0; } else { low = 1; } totalSpeed += fabs( oldPos[low][0] - o[low].origin[0] ); } numSpeed++; // save the positions for ( k = 0; k < 2; k++ ) { VectorCopy( o[k].origin, oldPos[k] ); } lastLow = low; } // record the speed anim->moveSpeed = (int)( ( totalSpeed / numSpeed ) * 1000.0 / anim->frameLerp ); } } /* ====================== CG_ParseAnimationFiles Read in all the configuration and script files for this model. ====================== */ static qboolean CG_ParseAnimationFiles( bg_character_t *character, const char *animationGroup, const char *animationScript ) { char filename[MAX_QPATH]; fileHandle_t f; int len; // set the name of the animationGroup and animationScript in the animModelInfo structure Q_strncpyz( character->animModelInfo->animationGroup, animationGroup, sizeof( character->animModelInfo->animationGroup ) ); Q_strncpyz( character->animModelInfo->animationScript, animationScript, sizeof( character->animModelInfo->animationScript ) ); BG_R_RegisterAnimationGroup( animationGroup, character->animModelInfo ); // calc movespeed values if required CG_CalcMoveSpeeds( character ); // load the script file len = trap_FS_FOpenFile( animationScript, &f, FS_READ ); if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( bigTextBuffer ) - 1 ) { CG_Printf( "File %s is too long\n", filename ); return qfalse; } trap_FS_Read( bigTextBuffer, len, f ); bigTextBuffer[len] = 0; trap_FS_FCloseFile( f ); // parse the text BG_AnimParseAnimScript( character->animModelInfo, &cgs.animScriptData, animationScript, bigTextBuffer ); return qtrue; } /* ================== CG_CheckForExistingAnimModelInfo If this player model has already been parsed, then use the existing information. Otherwise, set the modelInfo pointer to the first free slot. returns qtrue if existing model found, qfalse otherwise ================== */ static qboolean CG_CheckForExistingAnimModelInfo( const char *animationGroup, const char *animationScript, animModelInfo_t **animModelInfo ) { int i; animModelInfo_t *trav, *firstFree = NULL; for ( i = 0, trav = cgs.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, trav++ ) { if ( *trav->animationGroup && *trav->animationScript ) { if ( !Q_stricmp( trav->animationGroup, animationGroup ) && !Q_stricmp( trav->animationScript, animationScript ) ) { // found a match, use this animModelInfo *animModelInfo = trav; return qtrue; } } else if ( !firstFree ) { firstFree = trav; } } if ( !firstFree ) { CG_Error( "unable to find a free modelinfo slot, cannot continue\n" ); } else { *animModelInfo = firstFree; // clear the structure out ready for use memset( *animModelInfo, 0, sizeof( *animModelInfo ) ); } // qfalse signifies that we need to parse the information from the script files return qfalse; } /* ============== CG_RegisterAcc ============== */ static qboolean CG_RegisterAcc( const char *modelName, int *model, const char* skinname, qhandle_t* skin ) { char filename[MAX_QPATH]; *model = trap_R_RegisterModel( modelName ); if ( !*model ) { return qfalse; } COM_StripExtension( modelName, filename ); Q_strcat( filename, sizeof( filename ), va( "_%s.skin", skinname ) ); *skin = trap_R_RegisterSkin( filename ); return qtrue; } typedef struct { char *type; accType_t index; } acc_t; static acc_t cg_accessories[] = { { "md3_beltr", ACC_BELT_LEFT }, { "md3_beltl", ACC_BELT_RIGHT }, { "md3_belt", ACC_BELT }, { "md3_back", ACC_BACK }, { "md3_weapon", ACC_WEAPON }, { "md3_weapon2", ACC_WEAPON2 }, }; static int cg_numAccessories = sizeof( cg_accessories ) / sizeof( cg_accessories[0] ); static acc_t cg_headAccessories[] = { { "md3_hat", ACC_HAT }, { "md3_rank", ACC_RANK }, { "md3_hat2", ACC_MOUTH2 }, { "md3_hat3", ACC_MOUTH3 }, }; static int cg_numHeadAccessories = sizeof( cg_headAccessories ) / sizeof( cg_headAccessories[0] ); /* ==================== CG_RegisterCharacter ==================== */ qboolean CG_RegisterCharacter( const char *characterFile, bg_character_t *character ) { bg_characterDef_t characterDef; char *filename; char buf[MAX_QPATH]; char accessoryname[MAX_QPATH]; int i; memset( &characterDef, 0, sizeof( characterDef ) ); if ( !BG_ParseCharacterFile( characterFile, &characterDef ) ) { return qfalse; // the parser will provide the error message } // Register Mesh if ( !( character->mesh = trap_R_RegisterModel( characterDef.mesh ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register mesh '%s' referenced from '%s'\n", characterDef.mesh, characterFile ); } // Register Skin COM_StripExtension( characterDef.mesh, buf ); filename = va( "%s_%s.skin", buf, characterDef.skin ); if ( !( character->skin = trap_R_RegisterSkin( filename ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register skin '%s' referenced from '%s'\n", filename, characterFile ); } else { for ( i = 0; i < cg_numAccessories; i++ ) { if ( trap_R_GetSkinModel( character->skin, cg_accessories[i].type, accessoryname ) ) { if ( !CG_RegisterAcc( accessoryname, &character->accModels[cg_accessories[i].index], characterDef.skin, &character->accSkins[cg_accessories[i].index] ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register accessory '%s' referenced from '%s'->'%s'\n", accessoryname, characterFile, filename ); } } } for ( i = 0; i < cg_numHeadAccessories; i++ ) { if ( trap_R_GetSkinModel( character->skin, cg_headAccessories[i].type, accessoryname ) ) { if ( !CG_RegisterAcc( accessoryname, &character->accModels[cg_headAccessories[i].index], characterDef.skin, &character->accSkins[cg_headAccessories[i].index] ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register accessory '%s' referenced from '%s'->'%s'\n", accessoryname, characterFile, filename ); } } } } // Register Undressed Corpse Media if ( *characterDef.undressedCorpseModel ) { // Register Undressed Corpse Model if ( !( character->undressedCorpseModel = trap_R_RegisterModel( characterDef.undressedCorpseModel ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register undressed corpse model '%s' referenced from '%s'\n", characterDef.undressedCorpseModel, characterFile ); } // Register Undressed Corpse Skin COM_StripExtension( characterDef.undressedCorpseModel, buf ); filename = va( "%s_%s.skin", buf, characterDef.undressedCorpseSkin ); if ( !( character->undressedCorpseSkin = trap_R_RegisterSkin( filename ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register undressed corpse skin '%s' referenced from '%s'\n", filename, characterFile ); } } // Register the head for the hud if ( *characterDef.hudhead ) { // Register Hud Head Model if ( !( character->hudhead = trap_R_RegisterModel( characterDef.hudhead ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register hud head model '%s' referenced from '%s'\n", characterDef.hudhead, characterFile ); } if ( *characterDef.hudheadskin && !( character->hudheadskin = trap_R_RegisterSkin( characterDef.hudheadskin ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register hud head skin '%s' referenced from '%s'\n", characterDef.hudheadskin, characterFile ); } if ( *characterDef.hudheadanims ) { if ( !CG_ParseHudHeadConfig( characterDef.hudheadanims, character->hudheadanimations ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to register hud head animations '%s' referenced from '%s'\n", characterDef.hudheadanims, characterFile ); } } else { CG_Printf( S_COLOR_YELLOW "WARNING: no hud head animations supplied in '%s'\n", characterFile ); } } // Parse Animation Files if ( !CG_CheckForExistingAnimModelInfo( characterDef.animationGroup, characterDef.animationScript, &character->animModelInfo ) ) { if ( !CG_ParseAnimationFiles( character, characterDef.animationGroup, characterDef.animationScript ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to load animation files referenced from '%s'\n", characterFile ); return qfalse; } } // STILL MISSING: GIB MODELS (OPTIONAL?) return qtrue; } bg_character_t *CG_CharacterForClientinfo( clientInfo_t *ci, centity_t *cent ) { int team, cls; if ( cent && cent->currentState.eType == ET_CORPSE ) { if ( cent->currentState.onFireStart >= 0 ) { return cgs.gameCharacters[ cent->currentState.onFireStart ]; } else { if ( cent->currentState.modelindex < 4 ) { return BG_GetCharacter( cent->currentState.modelindex, cent->currentState.modelindex2 ); } else { return BG_GetCharacter( cent->currentState.modelindex - 4, cent->currentState.modelindex2 ); } } } if ( cent && cent->currentState.powerups & ( 1 << PW_OPS_DISGUISED ) ) { team = ci->team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS; cls = ( cent->currentState.powerups >> PW_OPS_CLASS_1 ) & 7; return BG_GetCharacter( team, cls ); } if ( ci->character ) { return ci->character; } return BG_GetCharacter( ci->team, ci->cls ); } bg_character_t *CG_CharacterForPlayerstate( playerState_t* ps ) { int team, cls; if ( ps->powerups[PW_OPS_DISGUISED] ) { team = cgs.clientinfo[ps->clientNum].team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS; cls = 0; if ( ps->powerups[PW_OPS_CLASS_1] ) { cls |= 1; } if ( ps->powerups[PW_OPS_CLASS_2] ) { cls |= 2; } if ( ps->powerups[PW_OPS_CLASS_3] ) { cls |= 4; } return BG_GetCharacter( team, cls ); } return BG_GetCharacter( cgs.clientinfo[ps->clientNum].team, cgs.clientinfo[ps->clientNum].cls ); } /* ======================== CG_RegisterPlayerClasses ======================== */ void CG_RegisterPlayerClasses( void ) { bg_playerclass_t *classInfo; bg_character_t *character; int team, cls; for ( team = TEAM_AXIS; team <= TEAM_ALLIES; team++ ) { for ( cls = PC_SOLDIER; cls < NUM_PLAYER_CLASSES; cls++ ) { classInfo = BG_GetPlayerClassInfo( team, cls ); character = BG_GetCharacter( team, cls ); Q_strncpyz( character->characterFile, classInfo->characterFile, sizeof( character->characterFile ) ); if ( !CG_RegisterCharacter( character->characterFile, character ) ) { CG_Error( "ERROR: CG_RegisterPlayerClasses: failed to load character file '%s' for the %s %s\n", character->characterFile, ( team == TEAM_AXIS ? "Axis" : "Allied" ), BG_ClassnameForNumber( classInfo->classNum ) ); } if ( !( classInfo->icon = trap_R_RegisterShaderNoMip( classInfo->iconName ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to load class icon '%s' for the %s %s\n", classInfo->iconName, ( team == TEAM_AXIS ? "Axis" : "Allied" ), BG_ClassnameForNumber( classInfo->classNum ) ); } if ( !( classInfo->arrow = trap_R_RegisterShaderNoMip( classInfo->iconArrow ) ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: failed to load icon arrow '%s' for the %s %s\n", classInfo->iconArrow, ( team == TEAM_AXIS ? "Axis" : "Allied" ), BG_ClassnameForNumber( classInfo->classNum ) ); } } } }