/* =========================================================================== Return to Castle Wolfenstein multiplayer GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). RTCW MP 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. RTCW MP 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 RTCW MP Source Code. If not, see . In addition, the RTCW MP 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 RTCW MP 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. =========================================================================== */ //=========================================================================== // bg_animation.c // // Incorporates several elements related to the new flexible animation system. // // This includes scripting elements, support routines for new animation set // reference system and other bits and pieces. // //=========================================================================== #include "q_shared.h" #include "bg_public.h" // JPW NERVE -- added because I need to check single/multiplayer instances and branch accordingly #ifdef CGAMEDLL extern vmCvar_t cg_gameType; #endif #ifdef GAMEDLL extern vmCvar_t g_gametype; #endif // debug defines, to prevent doing costly string cvar lookups //#define DBGANIMS //#define DBGANIMEVENTS // this is used globally within this file to reduce redundant params static animScriptData_t *globalScriptData = NULL; #define MAX_ANIM_DEFINES 16 static char *globalFilename; // to prevent redundant params static int parseClient; // these are used globally during script parsing static int numDefines[NUM_ANIM_CONDITIONS]; static char defineStrings[10000]; // stores the actual strings static int defineStringsOffset; static animStringItem_t defineStr[NUM_ANIM_CONDITIONS][MAX_ANIM_DEFINES]; static int defineBits[NUM_ANIM_CONDITIONS][MAX_ANIM_DEFINES][2]; static scriptAnimMoveTypes_t parseMovetype; static int parseEvent; animStringItem_t weaponStrings[WP_NUM_WEAPONS]; qboolean weaponStringsInited = qfalse; animStringItem_t animStateStr[] = { {"RELAXED", -1}, {"QUERY", -1}, {"ALERT", -1}, {"COMBAT", -1}, {NULL, -1}, }; static animStringItem_t animMoveTypesStr[] = { {"** UNUSED **", -1}, {"IDLE", -1}, {"IDLECR", -1}, {"WALK", -1}, {"WALKBK", -1}, {"WALKCR", -1}, {"WALKCRBK", -1}, {"RUN", -1}, {"RUNBK", -1}, {"SWIM", -1}, {"SWIMBK", -1}, {"STRAFERIGHT", -1}, {"STRAFELEFT", -1}, {"TURNRIGHT", -1}, {"TURNLEFT", -1}, {"CLIMBUP", -1}, {"CLIMBDOWN", -1}, {"FALLEN", -1}, // DHM - Nerve :: dead, before limbo {NULL, -1}, }; static animStringItem_t animEventTypesStr[] = { {"PAIN", -1}, {"DEATH", -1}, {"FIREWEAPON", -1}, {"JUMP", -1}, {"JUMPBK", -1}, {"LAND", -1}, {"DROPWEAPON", -1}, {"RAISEWEAPON", -1}, {"CLIMBMOUNT", -1}, {"CLIMBDISMOUNT", -1}, {"RELOAD", -1}, {"PICKUPGRENADE", -1}, {"KICKGRENADE", -1}, {"QUERY", -1}, {"INFORM_FRIENDLY_OF_ENEMY", -1}, {"KICK", -1}, {"REVIVE", -1}, {"FIRSTSIGHT", -1}, {"ROLL", -1}, {"FLIP", -1}, {"DIVE", -1}, {"PRONE_TO_CROUCH", -1}, {"BULLETIMPACT", -1}, {"INSPECTSOUND", -1}, {NULL, -1}, }; animStringItem_t animBodyPartsStr[] = { {"** UNUSED **", -1}, {"LEGS", -1}, {"TORSO", -1}, {"BOTH", -1}, {NULL, -1}, }; //------------------------------------------------------------ // conditions static animStringItem_t animConditionPositionsStr[] = { {"** UNUSED **", -1}, {"BEHIND", -1}, {"INFRONT", -1}, {"RIGHT", -1}, {"LEFT", -1}, {NULL, -1}, }; static animStringItem_t animConditionMountedStr[] = { {"** UNUSED **", -1}, {"MG42", -1}, {NULL, -1}, }; static animStringItem_t animConditionLeaningStr[] = { {"** UNUSED **", -1}, {"RIGHT", -1}, {"LEFT", -1}, {NULL, -1}, }; // !!! NOTE: this must be kept in sync with the tag names in ai_cast_characters.c static animStringItem_t animConditionImpactPointsStr[] = { {"** UNUSED **", -1}, {"HEAD", -1}, {"CHEST", -1}, {"GUT", -1}, {"GROIN", -1}, {"SHOULDER_RIGHT", -1}, {"SHOULDER_LEFT", -1}, {"KNEE_RIGHT", -1}, {"KNEE_LEFT", -1}, {NULL, -1}, }; // !!! NOTE: this must be kept in sync with the teams in ai_cast.h static animStringItem_t animEnemyTeamsStr[] = { {"NAZI", -1}, {"ALLIES", -1}, {"MONSTER", -1}, {"SPARE1", -1}, {"SPARE2", -1}, {"SPARE3", -1}, {"SPARE4", -1}, {"NEUTRAL", -1} }; static animStringItem_t animHealthLevelStr[] = { {"1", -1}, {"2", -1}, {"3", -1}, }; typedef enum { ANIM_CONDTYPE_BITFLAGS, ANIM_CONDTYPE_VALUE, NUM_ANIM_CONDTYPES } animScriptConditionTypes_t; typedef struct { animScriptConditionTypes_t type; animStringItem_t *values; } animConditionTable_t; static animStringItem_t animConditionsStr[] = { {"WEAPONS", -1}, {"ENEMY_POSITION", -1}, {"ENEMY_WEAPON", -1}, {"UNDERWATER", -1}, {"MOUNTED", -1}, {"MOVETYPE", -1}, {"UNDERHAND", -1}, {"LEANING", -1}, {"IMPACT_POINT", -1}, {"CROUCHING", -1}, {"STUNNED", -1}, {"FIRING", -1}, {"SHORT_REACTION", -1}, {"ENEMY_TEAM", -1}, {"PARACHUTE", -1}, {"CHARGING", -1}, {"SECONDLIFE", -1}, {"HEALTH_LEVEL", -1}, {NULL, -1}, }; static animConditionTable_t animConditionsTable[NUM_ANIM_CONDITIONS] = { {ANIM_CONDTYPE_BITFLAGS, weaponStrings}, {ANIM_CONDTYPE_BITFLAGS, animConditionPositionsStr}, {ANIM_CONDTYPE_BITFLAGS, weaponStrings}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, animConditionMountedStr}, {ANIM_CONDTYPE_BITFLAGS, animMoveTypesStr}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, animConditionLeaningStr}, {ANIM_CONDTYPE_VALUE, animConditionImpactPointsStr}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, animEnemyTeamsStr}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, NULL}, {ANIM_CONDTYPE_VALUE, animHealthLevelStr}, }; //------------------------------------------------------------ /* ================ return a hash value for the given string ================ */ static long BG_StringHashValue( const char *fname ) { int i; long hash; char letter; hash = 0; i = 0; while ( fname[i] != '\0' ) { letter = tolower( fname[i] ); hash += (long)( letter ) * ( i + 119 ); i++; } if ( hash == -1 ) { hash = 0; // never return -1 } return hash; } /* ================= BG_AnimParseError ================= */ void QDECL BG_AnimParseError( const char *msg, ... ) { va_list argptr; char text[1024]; va_start( argptr, msg ); Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); if ( globalFilename ) { Com_Error( ERR_DROP, "%s: (%s, line %i)", text, globalFilename, COM_GetCurrentParseLine() + 1 ); } else { Com_Error( ERR_DROP, "%s", text ); } } /* ================= BG_ModelInfoForClient ================= */ animModelInfo_t *BG_ModelInfoForClient( int client ) { if ( !globalScriptData ) { BG_AnimParseError( "BG_ModelInfoForClient: NULL globalScriptData" ); } // if ( !globalScriptData->clientModels[client] ) { BG_AnimParseError( "BG_ModelInfoForClient: client %i has no modelinfo", client ); } // return &globalScriptData->modelInfo[globalScriptData->clientModels[client] - 1]; } /* ================= BG_ModelInfoForModelname ================= */ animModelInfo_t *BG_ModelInfoForModelname( char *modelname ) { int i; animModelInfo_t *modelInfo; // if ( !globalScriptData ) { BG_AnimParseError( "BG_ModelInfoForModelname: NULL globalScriptData" ); } // for ( i = 0, modelInfo = globalScriptData->modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, modelInfo++ ) { if ( !modelInfo->modelname[0] ) { continue; } if ( !Q_stricmp( modelname, modelInfo->modelname ) ) { return modelInfo; } } // return NULL; } /* ================= BG_AnimationIndexForString ================= */ int BG_AnimationIndexForString( char *string, int client ) { int i, hash; animation_t *anim; animModelInfo_t *modelInfo; modelInfo = BG_ModelInfoForClient( client ); hash = BG_StringHashValue( string ); for ( i = 0, anim = modelInfo->animations; i < modelInfo->numAnimations; i++, anim++ ) { if ( ( hash == anim->nameHash ) && !Q_stricmp( string, anim->name ) ) { // found a match return i; } } // no match found BG_AnimParseError( "BG_AnimationIndexForString: unknown index '%s' for model '%s'", string, modelInfo->modelname ); return -1; // shutup compiler } /* ================= BG_AnimationForString ================= */ animation_t *BG_AnimationForString( char *string, animModelInfo_t *modelInfo ) { int i, hash; animation_t *anim; hash = BG_StringHashValue( string ); for ( i = 0, anim = modelInfo->animations; i < modelInfo->numAnimations; i++, anim++ ) { if ( ( hash == anim->nameHash ) && !Q_stricmp( string, anim->name ) ) { // found a match return anim; } } // no match found Com_Error( ERR_DROP, "BG_AnimationForString: unknown animation '%s' for model '%s'", string, modelInfo->modelname ); return NULL; // shutup compiler } /* ================= BG_IndexForString errors out if no match found ================= */ int BG_IndexForString( char *token, animStringItem_t *strings, qboolean allowFail ) { int i, hash; animStringItem_t *strav; hash = BG_StringHashValue( token ); for ( i = 0, strav = strings; strav->string; strav++, i++ ) { if ( strav->hash == -1 ) { strav->hash = BG_StringHashValue( strav->string ); } if ( ( hash == strav->hash ) && !Q_stricmp( token, strav->string ) ) { // found a match return i; } } // no match found if ( !allowFail ) { BG_AnimParseError( "BG_IndexForString: unknown token '%s'", token ); } // return -1; } /* =============== BG_CopyStringIntoBuffer =============== */ char *BG_CopyStringIntoBuffer( char *string, char *buffer, int bufSize, int *offset ) { char *pch; // check for overloaded buffer if ( *offset + strlen( string ) + 1 >= bufSize ) { BG_AnimParseError( "BG_CopyStringIntoBuffer: out of buffer space" ); } pch = &buffer[*offset]; // safe to do a strcpy since we've already checked for overrun strcpy( pch, string ); // move the offset along *offset += strlen( string ) + 1; return pch; } /* ============ BG_InitWeaponStrings Builds the list of weapon names from the item list. This is done here rather than hardcoded to ease the process of modifying the weapons. ============ */ void BG_InitWeaponStrings( void ) { int i; gitem_t *item; memset( weaponStrings, 0, sizeof( weaponStrings ) ); for ( i = 0; i < WP_NUM_WEAPONS; i++ ) { // find this weapon in the itemslist, and extract the name for ( item = bg_itemlist + 1; item->classname; item++ ) { if ( item->giType == IT_WEAPON && item->giTag == i ) { // found a match weaponStrings[i].string = item->pickup_name; weaponStrings[i].hash = BG_StringHashValue( weaponStrings[i].string ); break; } } if ( !item->classname ) { weaponStrings[i].string = "(unknown)"; weaponStrings[i].hash = BG_StringHashValue( weaponStrings[i].string ); } } weaponStringsInited = qtrue; } /* ============ BG_AnimParseAnimConfig returns qfalse if error, qtrue otherwise ============ */ qboolean BG_AnimParseAnimConfig( animModelInfo_t *animModelInfo, const char *filename, const char *input ) { char *text_p, *token; animation_t *animations; headAnimation_t *headAnims; int i, fps, skip = -1; if ( !weaponStringsInited ) { BG_InitWeaponStrings(); } globalFilename = (char *)filename; animations = animModelInfo->animations; animModelInfo->numAnimations = 0; headAnims = animModelInfo->headAnims; text_p = (char *)input; COM_BeginParseSession( "BG_AnimParseAnimConfig" ); animModelInfo->footsteps = FOOTSTEP_NORMAL; VectorClear( animModelInfo->headOffset ); animModelInfo->gender = GENDER_MALE; animModelInfo->isSkeletal = qfalse; animModelInfo->version = 0; // read optional parameters while ( 1 ) { 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" ) ) { animModelInfo->footsteps = FOOTSTEP_NORMAL; } else if ( !Q_stricmp( token, "boot" ) ) { animModelInfo->footsteps = FOOTSTEP_BOOT; } else if ( !Q_stricmp( token, "flesh" ) ) { animModelInfo->footsteps = FOOTSTEP_FLESH; } else if ( !Q_stricmp( token, "mech" ) ) { animModelInfo->footsteps = FOOTSTEP_MECH; } else if ( !Q_stricmp( token, "energy" ) ) { animModelInfo->footsteps = FOOTSTEP_ENERGY; } else { BG_AnimParseError( "Bad footsteps parm '%s'\n", token ); } continue; } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } animModelInfo->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' ) { animModelInfo->gender = GENDER_FEMALE; } else if ( token[0] == 'n' || token[0] == 'N' ) { animModelInfo->gender = GENDER_NEUTER; } else { animModelInfo->gender = GENDER_MALE; } continue; } else if ( !Q_stricmp( token, "version" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } animModelInfo->version = atoi( token ); continue; } else if ( !Q_stricmp( token, "skeletal" ) ) { animModelInfo->isSkeletal = qtrue; continue; } if ( animModelInfo->version < 2 ) { // if it is a number, start parsing animations if ( token[0] >= '0' && token[0] <= '9' ) { text_p -= strlen( token ); // unget the token break; } } // STARTANIMS marks the start of the animations if ( !Q_stricmp( token, "STARTANIMS" ) ) { break; } BG_AnimParseError( "unknown token '%s'", token ); } // read information for each frame for ( i = 0 ; ( animModelInfo->version > 1 ) || ( i < MAX_ANIMATIONS ) ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( animModelInfo->version > 1 ) { // includes animation names at start of each line if ( !Q_stricmp( token, "ENDANIMS" ) ) { // end of animations break; } Q_strncpyz( animations[i].name, token, sizeof( animations[i].name ) ); // convert to all lower case Q_strlwr( animations[i].name ); // token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "end of file without ENDANIMS" ); } } else { // just set it to the equivalent animStrings[] Q_strncpyz( animations[i].name, animStrings[i], sizeof( animations[i].name ) ); // convert to all lower case Q_strlwr( animations[i].name ); } animations[i].firstFrame = atoi( token ); if ( !animModelInfo->isSkeletal ) { // skeletal models dont require adjustment // leg only frames are adjusted to not count the upper body only frames if ( i == LEGS_WALKCR ) { skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; } if ( i >= LEGS_WALKCR ) { animations[i].firstFrame -= skip; } } token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "end of file without ENDANIMS" ); } animations[i].numFrames = atoi( token ); token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "end of file without ENDANIMS: line %i" ); } animations[i].loopFrames = atoi( token ); token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "end of file without ENDANIMS: line %i" ); } fps = atof( token ); if ( fps == 0 ) { fps = 1; } animations[i].frameLerp = 1000 / fps; animations[i].initialLerp = 1000 / fps; // movespeed token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "end of file without ENDANIMS" ); } animations[i].moveSpeed = atoi( token ); // animation blending token = COM_ParseExt( &text_p, qfalse ); // must be on same line if ( !token ) { animations[i].animBlend = 0; } else { animations[i].animBlend = atoi( token ); } // calculate the duration animations[i].duration = animations[i].initialLerp + animations[i].frameLerp * animations[i].numFrames + animations[i].animBlend; // get the nameHash animations[i].nameHash = BG_StringHashValue( animations[i].name ); if ( !Q_strncmp( animations[i].name, "climb", 5 ) ) { animations[i].flags |= ANIMFL_LADDERANIM; } if ( strstr( animations[i].name, "firing" ) ) { animations[i].flags |= ANIMFL_FIRINGANIM; animations[i].initialLerp = 40; } } animModelInfo->numAnimations = i; if ( animModelInfo->version < 2 && i != MAX_ANIMATIONS ) { BG_AnimParseError( "Incorrect number of animations" ); return qfalse; } // check for head anims token = COM_Parse( &text_p ); if ( token && token[0] ) { if ( animModelInfo->version < 2 || !Q_stricmp( token, "HEADFRAMES" ) ) { // read information for each head frame for ( i = 0 ; i < MAX_HEAD_ANIMS ; i++ ) { token = COM_Parse( &text_p ); if ( !token || !token[0] ) { break; } if ( animModelInfo->version > 1 ) { // includes animation names at start of each line // just throw this information away, not required for head token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { break; } } if ( !i ) { skip = atoi( token ); } headAnims[i].firstFrame = atoi( token ); // modify according to last frame of the main animations, since the head is totally seperate headAnims[i].firstFrame -= animations[MAX_ANIMATIONS - 1].firstFrame + animations[MAX_ANIMATIONS - 1].numFrames + skip; token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { break; } headAnims[i].numFrames = atoi( token ); // skip the movespeed token = COM_ParseExt( &text_p, qfalse ); } animModelInfo->numHeadAnims = i; if ( i != MAX_HEAD_ANIMS ) { BG_AnimParseError( "Incorrect number of head frames" ); return qfalse; } } } return qtrue; } /* ================= BG_ParseConditionBits convert the string into a single int containing bit flags, stopping at a ',' or end of line ================= */ void BG_ParseConditionBits( char **text_pp, animStringItem_t *stringTable, int condIndex, int result[2] ) { qboolean endFlag = qfalse; int indexFound; int /*indexBits,*/ tempBits[2]; char currentString[MAX_QPATH]; qboolean minus = qfalse; char *token; //indexBits = 0; currentString[0] = '\0'; memset( result, 0, sizeof( result ) ); memset( tempBits, 0, sizeof( tempBits ) ); while ( !endFlag ) { token = COM_ParseExt( text_pp, qfalse ); if ( !token || !token[0] ) { COM_RestoreParseSession( text_pp ); // go back to the previous token endFlag = qtrue; // done parsing indexes if ( !strlen( currentString ) ) { break; } } if ( !Q_stricmp( token, "," ) ) { endFlag = qtrue; // end of indexes } if ( !Q_stricmp( token, "none" ) ) { // first bit is always the "unused" bit COM_BitSet( result, 0 ); continue; } if ( !Q_stricmp( token, "none," ) ) { // first bit is always the "unused" bit COM_BitSet( result, 0 ); endFlag = qtrue; // end of indexes continue; } if ( !Q_stricmp( token, "NOT" ) ) { token = "MINUS"; // NOT is equivalent to MINUS } if ( !endFlag && Q_stricmp( token, "AND" ) && Q_stricmp( token, "MINUS" ) ) { // must be a index // check for a comma (end of indexes) if ( token[strlen( token ) - 1] == ',' ) { endFlag = qtrue; token[strlen( token ) - 1] = '\0'; } // append this to the currentString if ( strlen( currentString ) ) { Q_strcat( currentString, sizeof( currentString ), " " ); } Q_strcat( currentString, sizeof( currentString ), token ); } if ( !Q_stricmp( token, "AND" ) || !Q_stricmp( token, "MINUS" ) || endFlag ) { // process the currentString if ( !strlen( currentString ) ) { if ( endFlag ) { BG_AnimParseError( "BG_AnimParseAnimScript: unexpected end of condition" ); } else { // check for minus indexes to follow if ( !Q_stricmp( token, "MINUS" ) ) { minus = qtrue; continue; } BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); } } if ( !Q_stricmp( currentString, "all" ) ) { tempBits[0] = ~0x0; tempBits[1] = ~0x0; } else { // first check this string with our defines indexFound = BG_IndexForString( currentString, defineStr[condIndex], qtrue ); if ( indexFound >= 0 ) { // we have precalculated the bitflags for the defines tempBits[0] = defineBits[condIndex][indexFound][0]; tempBits[1] = defineBits[condIndex][indexFound][1]; } else { // convert the string into an index indexFound = BG_IndexForString( currentString, stringTable, qfalse ); // convert the index into a bitflag COM_BitSet( tempBits, indexFound ); } } // perform operation if ( minus ) { // subtract result[0] &= ~tempBits[0]; result[1] &= ~tempBits[1]; } else { // add result[0] |= tempBits[0]; result[1] |= tempBits[1]; } // clear the currentString currentString[0] = '\0'; // check for minus indexes to follow if ( !Q_stricmp( token, "MINUS" ) ) { minus = qtrue; } } } } /* ================= BG_ParseConditions returns qtrue if everything went ok, error drops otherwise ================= */ qboolean BG_ParseConditions( char **text_pp, animScriptItem_t *scriptItem ) { int conditionIndex, conditionValue[2]; char *token; conditionValue[0] = 0; conditionValue[1] = 0; while ( 1 ) { token = COM_ParseExt( text_pp, qfalse ); if ( !token || !token[0] ) { break; } // special case, "default" has no conditions if ( !Q_stricmp( token, "default" ) ) { return qtrue; } conditionIndex = BG_IndexForString( token, animConditionsStr, qfalse ); switch ( animConditionsTable[conditionIndex].type ) { case ANIM_CONDTYPE_BITFLAGS: BG_ParseConditionBits( text_pp, animConditionsTable[conditionIndex].values, conditionIndex, conditionValue ); break; case ANIM_CONDTYPE_VALUE: if ( animConditionsTable[conditionIndex].values ) { token = COM_ParseExt( text_pp, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected condition value, found end of line" ); // RF modification } // check for a comma (condition divider) if ( token[strlen( token ) - 1] == ',' ) { token[strlen( token ) - 1] = '\0'; } conditionValue[0] = BG_IndexForString( token, animConditionsTable[conditionIndex].values, qfalse ); } else { conditionValue[0] = 1; // not used, just check for a positive condition } break; default: // TTimo gcc: NUM_ANIM_CONDTYPES not handled in switch break; } // now append this condition to the item scriptItem->conditions[scriptItem->numConditions].index = conditionIndex; scriptItem->conditions[scriptItem->numConditions].value[0] = conditionValue[0]; scriptItem->conditions[scriptItem->numConditions].value[1] = conditionValue[1]; scriptItem->numConditions++; } if ( scriptItem->numConditions == 0 ) { BG_AnimParseError( "BG_ParseConditions: no conditions found" ); // RF mod } return qtrue; } /* ================= BG_ParseCommands ================= */ void BG_ParseCommands( char **input, animScriptItem_t *scriptItem, animModelInfo_t *modelInfo, animScriptData_t *scriptData ) { char *token; // TTimo gcc: might be used uninitialized animScriptCommand_t *command = NULL; int partIndex = 0; while ( 1 ) { // parse the body part token = COM_ParseExt( input, ( partIndex < 1 ) ); if ( !token || !token[0] ) { break; } if ( !Q_stricmp( token, "}" ) ) { // unget the bracket and get out of here *input -= strlen( token ); break; } // new command? if ( partIndex == 0 ) { // have we exceeded the maximum number of commands? if ( scriptItem->numCommands >= MAX_ANIMSCRIPT_ANIMCOMMANDS ) { BG_AnimParseError( "BG_ParseCommands: exceeded maximum number of animations (%i)", MAX_ANIMSCRIPT_ANIMCOMMANDS ); } command = &scriptItem->commands[scriptItem->numCommands++]; memset( command, 0, sizeof( command ) ); } command->bodyPart[partIndex] = BG_IndexForString( token, animBodyPartsStr, qtrue ); if ( command->bodyPart[partIndex] > 0 ) { // parse the animation token = COM_ParseExt( input, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "BG_ParseCommands: expected animation" ); } command->animIndex[partIndex] = BG_AnimationIndexForString( token, parseClient ); command->animDuration[partIndex] = modelInfo->animations[command->animIndex[partIndex]].duration; // if this is a locomotion, set the movetype of the animation so we can reverse engineer the movetype from the animation, on the client if ( parseMovetype != ANIM_MT_UNUSED && command->bodyPart[partIndex] != ANIM_BP_TORSO ) { modelInfo->animations[command->animIndex[partIndex]].movetype |= ( 1 << parseMovetype ); } // if this is a fireweapon event, then this is a firing animation if ( parseEvent == ANIM_ET_FIREWEAPON ) { modelInfo->animations[command->animIndex[partIndex]].flags |= ANIMFL_FIRINGANIM; modelInfo->animations[command->animIndex[partIndex]].initialLerp = 40; } // check for a duration for this animation instance token = COM_ParseExt( input, qfalse ); if ( token && token[0] ) { if ( !Q_stricmp( token, "duration" ) ) { // read the duration token = COM_ParseExt( input, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "BG_ParseCommands: expected duration value" ); } command->animDuration[partIndex] = atoi( token ); } else { // unget the token COM_RestoreParseSession( input ); } } else { COM_RestoreParseSession( input ); } if ( command->bodyPart[partIndex] != ANIM_BP_BOTH && partIndex++ < 1 ) { continue; // allow parsing of another bodypart } } else { // unget the token *input -= strlen( token ); } // parse optional parameters (sounds, etc) while ( 1 ) { token = COM_ParseExt( input, qfalse ); if ( !token || !token[0] ) { break; } if ( !Q_stricmp( token, "sound" ) ) { token = COM_ParseExt( input, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "BG_ParseCommands: expected sound" ); } // NOTE: only sound script are supported at this stage if ( strstr( token, ".wav" ) ) { BG_AnimParseError( "BG_ParseCommands: wav files not supported, only sound scripts" ); // RF mod } command->soundIndex = globalScriptData->soundIndex( token ); } else { // unknown?? BG_AnimParseError( "BG_ParseCommands: unknown parameter '%s'", token ); } } partIndex = 0; } } /* ================= BG_AnimParseAnimScript Parse the animation script for this model, converting it into run-time structures ================= */ typedef enum { PARSEMODE_DEFINES, PARSEMODE_ANIMATION, PARSEMODE_CANNED_ANIMATIONS, PARSEMODE_STATECHANGES, PARSEMODE_EVENTS } animScriptParseMode_t; static animStringItem_t animParseModesStr[] = { {"defines", -1}, {"animations", -1}, {"canned_animations", -1}, {"statechanges", -1}, {"events", -1}, {NULL, -1}, }; void BG_AnimParseAnimScript( animModelInfo_t *modelInfo, animScriptData_t *scriptData, int client, char *filename, char *input ) { #define MAX_INDENT_LEVELS 3 char *text_p, *token; animScriptParseMode_t parseMode; animScript_t *currentScript; animScriptItem_t tempScriptItem; // TTimo gcc: might be used unitialized animScriptItem_t *currentScriptItem = NULL; int indexes[MAX_INDENT_LEVELS], indentLevel, oldState, newParseMode; int i, defineType; // the scriptData passed into here must be the one this binary is using globalScriptData = scriptData; // current client being parsed parseClient = client; // start at the defines parseMode = PARSEMODE_DEFINES; // record which modelInfo this client is using scriptData->clientModels[client] = 1 + (int)( modelInfo - scriptData->modelInfo ); // init the global defines globalFilename = filename; memset( defineStr, 0, sizeof( defineStr ) ); memset( defineStrings, 0, sizeof( defineStrings ) ); memset( numDefines, 0, sizeof( numDefines ) ); defineStringsOffset = 0; for ( i = 0; i < MAX_INDENT_LEVELS; i++ ) indexes[i] = -1; indentLevel = 0; currentScript = NULL; text_p = input; COM_BeginParseSession( "BG_AnimParseAnimScript" ); // read in the weapon defines while ( 1 ) { token = COM_Parse( &text_p ); if ( !token || !token[0] ) { if ( indentLevel ) { BG_AnimParseError( "BG_AnimParseAnimScript: unexpected end of file: %s" ); } break; } // check for a new section newParseMode = BG_IndexForString( token, animParseModesStr, qtrue ); if ( newParseMode >= 0 ) { if ( indentLevel ) { BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } parseMode = newParseMode; parseMovetype = ANIM_MT_UNUSED; parseEvent = -1; continue; } switch ( parseMode ) { case PARSEMODE_DEFINES: if ( !Q_stricmp( token, "set" ) ) { // read in the define type token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected condition type string" ); // RF mod } defineType = BG_IndexForString( token, animConditionsStr, qfalse ); // read in the define token = COM_ParseExt( &text_p, qfalse ); if ( !token || !token[0] ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected condition define string" ); // RF mod } // copy the define to the strings list defineStr[defineType][numDefines[defineType]].string = BG_CopyStringIntoBuffer( token, defineStrings, sizeof( defineStrings ), &defineStringsOffset ); defineStr[defineType][numDefines[defineType]].hash = BG_StringHashValue( defineStr[defineType][numDefines[defineType]].string ); // expecting an = token = COM_ParseExt( &text_p, qfalse ); if ( !token ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected '=', found end of line" ); // RF mod } if ( Q_stricmp( token, "=" ) ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected '=', found '%s'", token ); // RF mod } // parse the bits BG_ParseConditionBits( &text_p, animConditionsTable[defineType].values, defineType, defineBits[defineType][numDefines[defineType]] ); numDefines[defineType]++; // copy the weapon defines over to the enemy_weapon defines memcpy( &defineStr[ANIM_COND_ENEMY_WEAPON][0], &defineStr[ANIM_COND_WEAPON][0], sizeof( animStringItem_t ) * MAX_ANIM_DEFINES ); memcpy( &defineBits[ANIM_COND_ENEMY_WEAPON][0], &defineBits[ANIM_COND_WEAPON][0], sizeof( defineBits[ANIM_COND_ENEMY_WEAPON][0] ) * MAX_ANIM_DEFINES ); numDefines[ANIM_COND_ENEMY_WEAPON] = numDefines[ANIM_COND_WEAPON]; } break; case PARSEMODE_ANIMATION: case PARSEMODE_CANNED_ANIMATIONS: if ( !Q_stricmp( token, "{" ) ) { // about to increment indent level, check that we have enough information to do this if ( indentLevel >= MAX_INDENT_LEVELS ) { // too many indentations BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } if ( indexes[indentLevel] < 0 ) { // we havent found out what this new group is yet BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } // indentLevel++; } else if ( !Q_stricmp( token, "}" ) ) { // reduce the indentLevel indentLevel--; if ( indentLevel < 0 ) { BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } if ( indentLevel == 1 ) { currentScript = NULL; } // make sure we read a new index before next indent indexes[indentLevel] = -1; } else if ( indentLevel == 0 && ( indexes[indentLevel] < 0 ) ) { if ( Q_stricmp( token, "state" ) ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected 'state'" ); // RF mod } // read in the state type token = COM_ParseExt( &text_p, qfalse ); if ( !token ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected state type" ); // RF mod } indexes[indentLevel] = BG_IndexForString( token, animStateStr, qfalse ); //----(SA) // RF mod // check for the open bracket token = COM_ParseExt( &text_p, qtrue ); if ( !token || Q_stricmp( token, "{" ) ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected '{'" ); } indentLevel++; //----(SA) // RF mod } else if ( ( indentLevel == 1 ) && ( indexes[indentLevel] < 0 ) ) { // we are expecting a movement type indexes[indentLevel] = BG_IndexForString( token, animMoveTypesStr, qfalse ); if ( parseMode == PARSEMODE_ANIMATION ) { currentScript = &modelInfo->scriptAnims[indexes[0]][indexes[1]]; parseMovetype = indexes[1]; } else if ( parseMode == PARSEMODE_CANNED_ANIMATIONS ) { currentScript = &modelInfo->scriptCannedAnims[indexes[0]][indexes[1]]; } memset( currentScript, 0, sizeof( *currentScript ) ); } else if ( ( indentLevel == 2 ) && ( indexes[indentLevel] < 0 ) ) { // we are expecting a condition specifier // move the text_p backwards so we can read in the last token again text_p -= strlen( token ); // sanity check that if ( Q_strncmp( text_p, token, strlen( token ) ) ) { // this should never happen, just here to check that this operation is correct before code goes live BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); } // memset( &tempScriptItem, 0, sizeof( tempScriptItem ) ); indexes[indentLevel] = BG_ParseConditions( &text_p, &tempScriptItem ); // do we have enough room in this script for another item? if ( currentScript->numItems >= MAX_ANIMSCRIPT_ITEMS ) { BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum items per script (%i)", MAX_ANIMSCRIPT_ITEMS ); // RF mod } // are there enough items left in the global list? if ( modelInfo->numScriptItems >= MAX_ANIMSCRIPT_ITEMS_PER_MODEL ) { BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum global items (%i)", MAX_ANIMSCRIPT_ITEMS_PER_MODEL ); // RF mod } // it was parsed ok, so grab an item from the global list to use currentScript->items[currentScript->numItems] = &modelInfo->scriptItems[ modelInfo->numScriptItems++ ]; currentScriptItem = currentScript->items[currentScript->numItems]; currentScript->numItems++; // copy the data across from the temp script item *currentScriptItem = tempScriptItem; } else if ( indentLevel == 3 ) { // we are reading the commands, so parse this line as if it were a command // move the text_p backwards so we can read in the last token again text_p -= strlen( token ); // sanity check that if ( Q_strncmp( text_p, token, strlen( token ) ) ) { // this should never happen, just here to check that this operation is correct before code goes live BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); } // BG_ParseCommands( &text_p, currentScriptItem, modelInfo, scriptData ); } else { // huh ?? BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } break; case PARSEMODE_STATECHANGES: case PARSEMODE_EVENTS: if ( !Q_stricmp( token, "{" ) ) { // about to increment indent level, check that we have enough information to do this if ( indentLevel >= MAX_INDENT_LEVELS ) { // too many indentations BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } if ( indexes[indentLevel] < 0 ) { // we havent found out what this new group is yet BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } // indentLevel++; } else if ( !Q_stricmp( token, "}" ) ) { // reduce the indentLevel indentLevel--; if ( indentLevel < 0 ) { BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } if ( indentLevel == 0 ) { currentScript = NULL; } // make sure we read a new index before next indent indexes[indentLevel] = -1; } else if ( indentLevel == 0 && ( indexes[indentLevel] < 0 ) ) { if ( parseMode == PARSEMODE_STATECHANGES ) { if ( Q_stricmp( token, "statechange" ) ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected 'statechange', got '%s'", token ); // RF mod } // read in the old state type token = COM_ParseExt( &text_p, qfalse ); if ( !token ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected " ); // RF mod } oldState = BG_IndexForString( token, animStateStr, qfalse ); // read in the new state type token = COM_ParseExt( &text_p, qfalse ); if ( !token ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected " ); // RF mod } indexes[indentLevel] = BG_IndexForString( token, animStateStr, qfalse ); currentScript = &modelInfo->scriptStateChange[oldState][indexes[indentLevel]]; //----(SA) // RF mod // check for the open bracket token = COM_ParseExt( &text_p, qtrue ); if ( !token || Q_stricmp( token, "{" ) ) { BG_AnimParseError( "BG_AnimParseAnimScript: expected '{'" ); } indentLevel++; //----(SA) // RF mod } else { // read in the event type indexes[indentLevel] = BG_IndexForString( token, animEventTypesStr, qfalse ); currentScript = &modelInfo->scriptEvents[indexes[0]]; parseEvent = indexes[indentLevel]; } memset( currentScript, 0, sizeof( *currentScript ) ); } else if ( ( indentLevel == 1 ) && ( indexes[indentLevel] < 0 ) ) { // we are expecting a condition specifier // move the text_p backwards so we can read in the last token again text_p -= strlen( token ); // sanity check that if ( Q_strncmp( text_p, token, strlen( token ) ) ) { // this should never happen, just here to check that this operation is correct before code goes live BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); } // memset( &tempScriptItem, 0, sizeof( tempScriptItem ) ); indexes[indentLevel] = BG_ParseConditions( &text_p, &tempScriptItem ); // do we have enough room in this script for another item? if ( currentScript->numItems >= MAX_ANIMSCRIPT_ITEMS ) { BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum items per script (%i)", MAX_ANIMSCRIPT_ITEMS ); // RF mod } // are there enough items left in the global list? if ( modelInfo->numScriptItems >= MAX_ANIMSCRIPT_ITEMS_PER_MODEL ) { BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum global items (%i)", MAX_ANIMSCRIPT_ITEMS_PER_MODEL ); // RF mod } // it was parsed ok, so grab an item from the global list to use currentScript->items[currentScript->numItems] = &modelInfo->scriptItems[ modelInfo->numScriptItems++ ]; currentScriptItem = currentScript->items[currentScript->numItems]; currentScript->numItems++; // copy the data across from the temp script item *currentScriptItem = tempScriptItem; } else if ( indentLevel == 2 ) { // we are reading the commands, so parse this line as if it were a command // move the text_p backwards so we can read in the last token again text_p -= strlen( token ); // sanity check that if ( Q_strncmp( text_p, token, strlen( token ) ) ) { // this should never happen, just here to check that this operation is correct before code goes live BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); } // BG_ParseCommands( &text_p, currentScriptItem, modelInfo, scriptData ); } else { // huh ?? BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod } } } globalFilename = NULL; } //------------------------------------------------------------------------ // // run-time gameplay functions, these are called during gameplay, so they must be // cpu efficient. // /* =============== BG_EvaluateConditions returns qfalse if the set of conditions fails, qtrue otherwise =============== */ qboolean BG_EvaluateConditions( int client, animScriptItem_t *scriptItem ) { int i; animScriptCondition_t *cond; for ( i = 0, cond = scriptItem->conditions; i < scriptItem->numConditions; i++, cond++ ) { switch ( animConditionsTable[cond->index].type ) { case ANIM_CONDTYPE_BITFLAGS: if ( !( globalScriptData->clientConditions[client][cond->index][0] & cond->value[0] ) && !( globalScriptData->clientConditions[client][cond->index][1] & cond->value[1] ) ) { return qfalse; } break; case ANIM_CONDTYPE_VALUE: if ( !( globalScriptData->clientConditions[client][cond->index][0] == cond->value[0] ) ) { return qfalse; } break; default: // TTimo NUM_ANIM_CONDTYPES not handled break; } } // // all conditions must have passed return qtrue; } /* =============== BG_FirstValidItem scroll through the script items, returning the first script found to pass all conditions returns NULL if no match found =============== */ animScriptItem_t *BG_FirstValidItem( int client, animScript_t *script ) { animScriptItem_t **ppScriptItem; int i; for ( i = 0, ppScriptItem = script->items; i < script->numItems; i++, ppScriptItem++ ) { if ( BG_EvaluateConditions( client, *ppScriptItem ) ) { return *ppScriptItem; } } // return NULL; } /* =============== BG_PlayAnim =============== */ int BG_PlayAnim( playerState_t *ps, int animNum, animBodyPart_t bodyPart, int forceDuration, qboolean setTimer, qboolean isContinue, qboolean force ) { int duration; qboolean wasSet = qfalse; animModelInfo_t *modelInfo; modelInfo = BG_ModelInfoForClient( ps->clientNum ); if ( forceDuration ) { duration = forceDuration; } else { duration = modelInfo->animations[animNum].duration + 50; // account for lerping between anims } switch ( bodyPart ) { case ANIM_BP_BOTH: case ANIM_BP_LEGS: if ( ( ps->legsTimer < 50 ) || force ) { if ( !isContinue || !( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == animNum ) ) { wasSet = qtrue; ps->legsAnim = ( ( ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | animNum; if ( setTimer ) { ps->legsTimer = duration; } } else if ( setTimer && modelInfo->animations[animNum].loopFrames ) { ps->legsTimer = duration; } } if ( bodyPart == ANIM_BP_LEGS ) { break; } case ANIM_BP_TORSO: if ( ( ps->torsoTimer < 50 ) || force ) { if ( !isContinue || !( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == animNum ) ) { ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | animNum; if ( setTimer ) { ps->torsoTimer = duration; } } else if ( setTimer && modelInfo->animations[animNum].loopFrames ) { ps->torsoTimer = duration; } } break; default: // TTimo default ANIM_BP_UNUSED NUM_ANIM_BODYPARTS not handled break; } if ( !wasSet ) { return -1; } return duration; } /* =============== BG_PlayAnimName =============== */ int BG_PlayAnimName( playerState_t *ps, char *animName, animBodyPart_t bodyPart, qboolean setTimer, qboolean isContinue, qboolean force ) { return BG_PlayAnim( ps, BG_AnimationIndexForString( animName, ps->clientNum ), bodyPart, 0, setTimer, isContinue, force ); } /* =============== BG_ExecuteCommand returns the duration of the animation, -1 if no anim was set =============== */ int BG_ExecuteCommand( playerState_t *ps, animScriptCommand_t *scriptCommand, qboolean setTimer, qboolean isContinue, qboolean force ) { int duration = -1; qboolean playedLegsAnim = qfalse; if ( scriptCommand->bodyPart[0] ) { duration = scriptCommand->animDuration[0] + 50; // FIXME: how to sync torso/legs anims accounting for transition blends, etc if ( scriptCommand->bodyPart[0] == ANIM_BP_BOTH || scriptCommand->bodyPart[0] == ANIM_BP_LEGS ) { playedLegsAnim = ( BG_PlayAnim( ps, scriptCommand->animIndex[0], scriptCommand->bodyPart[0], duration, setTimer, isContinue, force ) > -1 ); } else { BG_PlayAnim( ps, scriptCommand->animIndex[0], scriptCommand->bodyPart[0], duration, setTimer, isContinue, force ); } } if ( scriptCommand->bodyPart[1] ) { duration = scriptCommand->animDuration[0] + 50; // FIXME: how to sync torso/legs anims accounting for transition blends, etc // just play the animation for the torso if ( scriptCommand->bodyPart[1] == ANIM_BP_BOTH || scriptCommand->bodyPart[1] == ANIM_BP_LEGS ) { playedLegsAnim = ( BG_PlayAnim( ps, scriptCommand->animIndex[1], scriptCommand->bodyPart[1], duration, setTimer, isContinue, force ) > -1 ); } else { BG_PlayAnim( ps, scriptCommand->animIndex[1], scriptCommand->bodyPart[1], duration, setTimer, isContinue, force ); } } if ( scriptCommand->soundIndex ) { globalScriptData->playSound( scriptCommand->soundIndex, ps->origin, ps->clientNum ); } if ( !playedLegsAnim ) { return -1; } return duration; } /* ================ BG_AnimScriptAnimation runs the normal locomotive animations returns 1 if an animation was set, -1 if no animation was found, 0 otherwise ================ */ int BG_AnimScriptAnimation( playerState_t *ps, aistateEnum_t state, scriptAnimMoveTypes_t movetype, qboolean isContinue ) { animModelInfo_t *modelInfo = NULL; animScript_t *script = NULL; animScriptItem_t *scriptItem = NULL; animScriptCommand_t *scriptCommand = NULL; // DHM - Nerve :: Allow fallen movetype while dead if ( ps->eFlags & EF_DEAD && movetype != ANIM_MT_FALLEN ) { return -1; } modelInfo = BG_ModelInfoForClient( ps->clientNum ); #ifdef DBGANIMS if ( ps->clientNum ) { Com_Printf( "script anim: cl %i, mt %s, ", ps->clientNum, animMoveTypesStr[movetype] ); } #endif // try finding a match in all states below the given state while ( !scriptItem && state >= 0 ) { script = &modelInfo->scriptAnims[ state ][ movetype ]; if ( !script->numItems ) { state--; continue; } // find the first script item, that passes all the conditions for this event scriptItem = BG_FirstValidItem( ps->clientNum, script ); if ( !scriptItem ) { state--; continue; } } // if ( !scriptItem ) { #ifdef DBGANIMS if ( ps->clientNum ) { Com_Printf( "no valid conditions\n" ); } #endif return -1; } // save this as our current movetype BG_UpdateConditionValue( ps->clientNum, ANIM_COND_MOVETYPE, movetype, qtrue ); // pick the correct animation for this character (animations must be constant for each character, otherwise they'll constantly change) scriptCommand = &scriptItem->commands[ ps->clientNum % scriptItem->numCommands ]; #ifdef DBGANIMS if ( scriptCommand->bodyPart[0] ) { if ( ps->clientNum ) { Com_Printf( "anim0 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[0]].string, modelInfo->animations[scriptCommand->animIndex[0]].name ); } } if ( scriptCommand->bodyPart[1] ) { if ( ps->clientNum ) { Com_Printf( "anim1 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[1]].string, modelInfo->animations[scriptCommand->animIndex[1]].name ); } } if ( ps->clientNum ) { Com_Printf( "\n" ); } #endif // run it return ( BG_ExecuteCommand( ps, scriptCommand, qfalse, isContinue, qfalse ) != -1 ); } /* ================ BG_AnimScriptCannedAnimation uses the current movetype for this client to play a canned animation returns the duration in milliseconds that this model should be paused. -1 if no anim found ================ */ int BG_AnimScriptCannedAnimation( playerState_t *ps, aistateEnum_t state ) { animModelInfo_t *modelInfo; animScript_t *script; animScriptItem_t *scriptItem; animScriptCommand_t *scriptCommand; scriptAnimMoveTypes_t movetype; if ( ps->eFlags & EF_DEAD ) { return -1; } movetype = globalScriptData->clientConditions[ ps->clientNum ][ ANIM_COND_MOVETYPE ][0]; if ( !movetype ) { // no valid movetype yet for this client return -1; } // modelInfo = BG_ModelInfoForClient( ps->clientNum ); script = &modelInfo->scriptCannedAnims[ state ][ movetype ]; if ( !script->numItems ) { return -1; } // find the first script item, that passes all the conditions for this event scriptItem = BG_FirstValidItem( ps->clientNum, script ); if ( !scriptItem ) { return -1; } // pick a random command scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; // run it return BG_ExecuteCommand( ps, scriptCommand, qtrue, qfalse, qfalse ); } /* ================ BG_AnimScriptStateChange returns the duration in milliseconds that this model should be paused. -1 if no anim found ================ */ int BG_AnimScriptStateChange( playerState_t *ps, aistateEnum_t newState, aistateEnum_t oldState ) { animModelInfo_t *modelInfo; animScript_t *script; animScriptItem_t *scriptItem; animScriptCommand_t *scriptCommand; if ( ps->eFlags & EF_DEAD ) { return -1; } modelInfo = BG_ModelInfoForClient( ps->clientNum ); script = &modelInfo->scriptStateChange[ oldState ][ newState ]; if ( !script->numItems ) { return -1; } // find the first script item, that passes all the conditions for this event scriptItem = BG_FirstValidItem( ps->clientNum, script ); if ( !scriptItem ) { return -1; } // pick a random command scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; // run it return BG_ExecuteCommand( ps, scriptCommand, qtrue, qfalse, qfalse ); } /* ================ BG_AnimScriptEvent returns the duration in milliseconds that this model should be paused. -1 if no event found ================ */ int BG_AnimScriptEvent( playerState_t *ps, scriptAnimEventTypes_t event, qboolean isContinue, qboolean force ) { animModelInfo_t *modelInfo; animScript_t *script; animScriptItem_t *scriptItem; animScriptCommand_t *scriptCommand; if ( event != ANIM_ET_DEATH && ps->eFlags & EF_DEAD ) { return -1; } #ifdef DBGANIMEVENTS Com_Printf( "script event: cl %i, ev %s, ", ps->clientNum, animEventTypesStr[event] ); #endif modelInfo = BG_ModelInfoForClient( ps->clientNum ); script = &modelInfo->scriptEvents[ event ]; if ( !script->numItems ) { #ifdef DBGANIMEVENTS Com_Printf( "no entry\n" ); #endif return -1; } // find the first script item, that passes all the conditions for this event scriptItem = BG_FirstValidItem( ps->clientNum, script ); if ( !scriptItem ) { #ifdef DBGANIMEVENTS Com_Printf( "no valid conditions\n" ); #endif return -1; } // pick a random command scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; #ifdef DBGANIMEVENTS if ( scriptCommand->bodyPart[0] ) { Com_Printf( "anim0 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[0]].string, modelInfo->animations[scriptCommand->animIndex[0]].name ); } if ( scriptCommand->bodyPart[1] ) { Com_Printf( "anim1 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[1]].string, modelInfo->animations[scriptCommand->animIndex[1]].name ); } Com_Printf( "\n" ); #endif // run it return BG_ExecuteCommand( ps, scriptCommand, qtrue, isContinue, force ); } /* =============== BG_ValidAnimScript returns qtrue if the given client has animation scripts =============== */ qboolean BG_ValidAnimScript( int clientNum ) { if ( !globalScriptData->clientModels[clientNum] ) { return qfalse; } // if ( !globalScriptData->modelInfo[ globalScriptData->clientModels[clientNum] ].numScriptItems ) { return qfalse; } // return qtrue; } /* =============== BG_GetAnimString =============== */ char *BG_GetAnimString( int client, int anim ) { animModelInfo_t *modelinfo = BG_ModelInfoForClient( client ); // if ( anim >= modelinfo->numAnimations ) { BG_AnimParseError( "BG_GetAnimString: anim index is out of range" ); } // return modelinfo->animations[anim].name; } /* ============== BG_UpdateConditionValue ============== */ void BG_UpdateConditionValue( int client, int condition, int value, qboolean checkConversion ) { if ( checkConversion ) { // we may need to convert to bitflags if ( animConditionsTable[condition].type == ANIM_CONDTYPE_BITFLAGS ) { // DHM - Nerve :: We want to set the ScriptData to the explicit value passed in. // COM_BitSet will OR values on top of each other, so clear it first. globalScriptData->clientConditions[client][condition][0] = 0; globalScriptData->clientConditions[client][condition][1] = 0; // dhm - end COM_BitSet( globalScriptData->clientConditions[client][condition], value ); return; } } globalScriptData->clientConditions[client][condition][0] = value; } /* ============== BG_GetConditionValue ============== */ int BG_GetConditionValue( int client, int condition, qboolean checkConversion ) { int value, i; // TTimo gcc: assignment makes integer from pointer without a cast value = (int)globalScriptData->clientConditions[client][condition]; if ( checkConversion ) { // we may need to convert to a value if ( animConditionsTable[condition].type == ANIM_CONDTYPE_BITFLAGS ) { //if (!value) // return 0; for ( i = 0; i < 8 * sizeof( globalScriptData->clientConditions[0][0] ); i++ ) { if ( COM_BitCheck( globalScriptData->clientConditions[client][condition], i ) ) { return i; } } // nothing found return 0; //BG_AnimParseError( "BG_GetConditionValue: internal error" ); } } return value; } /* ================ BG_GetAnimScriptAnimation returns the locomotion animation index, -1 if no animation was found, 0 otherwise ================ */ int BG_GetAnimScriptAnimation( int client, aistateEnum_t state, scriptAnimMoveTypes_t movetype ) { animModelInfo_t *modelInfo; animScript_t *script; animScriptItem_t *scriptItem = NULL; animScriptCommand_t *scriptCommand; modelInfo = BG_ModelInfoForClient( client ); // try finding a match in all states below the given state while ( !scriptItem && state >= 0 ) { script = &modelInfo->scriptAnims[ state ][ movetype ]; if ( !script->numItems ) { state--; continue; } // find the first script item, that passes all the conditions for this event scriptItem = BG_FirstValidItem( client, script ); if ( !scriptItem ) { state--; continue; } } // if ( !scriptItem ) { return -1; } // pick the correct animation for this character (animations must be constant for each character, otherwise they'll constantly change) scriptCommand = &scriptItem->commands[ client % scriptItem->numCommands ]; if ( !scriptCommand->bodyPart[0] ) { return -1; } // return the animation return scriptCommand->animIndex[0]; } /* ================ BG_GetAnimScriptEvent returns the animation index for this event ================ */ int BG_GetAnimScriptEvent( playerState_t *ps, scriptAnimEventTypes_t event ) { animModelInfo_t *modelInfo; animScript_t *script; animScriptItem_t *scriptItem; animScriptCommand_t *scriptCommand; if ( event != ANIM_ET_DEATH && ps->eFlags & EF_DEAD ) { return -1; } modelInfo = BG_ModelInfoForClient( ps->clientNum ); script = &modelInfo->scriptEvents[ event ]; if ( !script->numItems ) { return -1; } // find the first script item, that passes all the conditions for this event scriptItem = BG_FirstValidItem( ps->clientNum, script ); if ( !scriptItem ) { return -1; } // pick a random command scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; // return the animation return scriptCommand->animIndex[0]; } /* =============== BG_GetAnimationForIndex returns the animation_t for the given index =============== */ animation_t *BG_GetAnimationForIndex( int client, int index ) { animModelInfo_t *modelInfo; modelInfo = BG_ModelInfoForClient( client ); if ( index < 0 || index >= modelInfo->numAnimations ) { Com_Error( ERR_DROP, "BG_GetAnimationForIndex: index out of bounds" ); } return &modelInfo->animations[index]; } /* ================= BG_AnimUpdatePlayerStateConditions ================= */ void BG_AnimUpdatePlayerStateConditions( pmove_t *pmove ) { playerState_t *ps = pmove->ps; // WEAPON if ( ps->eFlags & EF_ZOOMING ) { BG_UpdateConditionValue( ps->clientNum, ANIM_COND_WEAPON, WP_BINOCULARS, qtrue ); } else { BG_UpdateConditionValue( ps->clientNum, ANIM_COND_WEAPON, ps->weapon, qtrue ); } // MOUNTED if ( ps->eFlags & EF_MG42_ACTIVE ) { BG_UpdateConditionValue( ps->clientNum, ANIM_COND_MOUNTED, MOUNTED_MG42, qtrue ); } else { BG_UpdateConditionValue( ps->clientNum, ANIM_COND_MOUNTED, MOUNTED_UNUSED, qtrue ); } // UNDERHAND BG_UpdateConditionValue( ps->clientNum, ANIM_COND_UNDERHAND, ps->viewangles[0] > 0, qtrue ); if ( ps->viewheight == ps->crouchViewHeight ) { ps->eFlags |= EF_CROUCHING; } else { ps->eFlags &= ~EF_CROUCHING; } if ( pmove->cmd.buttons & BUTTON_ATTACK ) { BG_UpdateConditionValue( ps->clientNum, ANIM_COND_FIRING, qtrue, qtrue ); } else { BG_UpdateConditionValue( ps->clientNum, ANIM_COND_FIRING, qfalse, qtrue ); } }