diff --git a/cgame/cg_players.c b/cgame/cg_players.c index 94f0a94..911d32e 100644 --- a/cgame/cg_players.c +++ b/cgame/cg_players.c @@ -398,7 +398,6 @@ static int CG_ParseAnimationSndFile( const char *filename, int animFileIndex ) return i; } #else -/* TODO: dynamic file size? */ static int CG_ParseAnimationSndFile( const char *filename, int animFileIndex ) { char *text_p; @@ -526,6 +525,7 @@ A lot more efficient considering how many freakin more animations we introduced */ // //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; @@ -715,6 +715,208 @@ static int CG_ParseAnimationFile( const char* filename/*, clientInfo_t *ci*/ ) { 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 /* ====================== @@ -1050,7 +1252,7 @@ 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; @@ -1482,6 +1684,458 @@ static qboolean CG_ParseModelDataFile( clientInfo_t *ci, const char *charName, //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 /* =============================================================================