news
about
story
files


in game
characters
weapons
concept art


tutorials
forums


coyote

ishtar

Tutorials

First person animation
By Coyote ( Joseph Williams )

If you use any of this tutorial, give me some props.

Probably the one thing most Quake 3 MODs deal with when they first start out is first person perspectives and how to separate them from the third world view so that you can easily animate the first person and add in hands to the weapon models. While this is not the only way of handling this and not the only reason youd want to do this, this tutorial will step you through the process needed to animate a model in first person view. You will not learn about how to add in additional animations beyond allowing the weapon to fire, though as youll see adding in other animations is extremely easy once everything is set up. Also this tutorial only adds in changes to the shotgun and machinegun, but as you look over the tutorial, this is simple to help keep the tutorial a bit shorter as adding in the entire code block to add all weapons would add another page or so.



Files well be editing in this tutorial:
game/bg_public.h
cgame/cg_local.h
cgame/cg_players.c
cgame/cg_weapons.c
game/bg_pmove.c


Color Legend:
Red : Files and functions that are changed in that step
Blue : Code that I have changed from the original
Green: Code segments
White: Original code or tutorial text

STEP 1:

Well first thing we need to know is how many animations we would like to perform in first person. The best way to handle this is to create an enumerated data type (this is a type that normal starts counting 0, 1, 2, 3, etc) but allows you to have a name that represents each number.

Add to file: game/bg_public.h

Code:
----------------------------------------------------------------------

typedef enum {
WP_ANIM_READY,
WP_ANIM_FIRE,
MAX_WEAPON_ANIMATIONS
} wpAnimNumber_t;

----------------------------------------------------------------------

As you can see this is a rather simple number of animations, to add more you just start adding them under the WP_ANIM_FIRE.


STEP 2:

Next we need a place to be able to store our first person model, the animation frames, and a hand model so that we get a constant appearance across all weapons. There just so happens to be a nice struct called weaponInfo_s that is in the cgame/cg_local.h. This structure is a list of variables for each weapon. We need to add in three variables to it.

Add to file: cgame/cg_local.h
Add to struct: weaponInfo_s


Code:
----------------------------------------------------------------------

qhandle_t animModel;
qhandle_t animHandModel;

animation_t animations[MAX_WEAPON_ANIMATIONS];

----------------------------------------------------------------------

STEP 3:

Now were going to need a way to know which frames were on currently in regard to the current animation. This is a simple addition of a variable to a structure in the cg_local.h file.

Add to file: cgame/cg_local.h
Add to struct: playerEntity_t


Code:
----------------------------------------------------------------------
lerpFrame_t weapon;
----------------------------------------------------------------------




STEP 4:
Ok great your saying, we have all these variables and structs and things but how the heck are we going to get the models and the numbers into the game? Fortunately thanks to the CG_ParseAnimationFile function in cg_player.c it was easy to adapt its design to our needs, what the function below does is parse a animation.cfg file that will store beginning frames, number of frames, frames to repeat, and frames per second for each animation, also is a example of a animation.cfg file and what each number means.

Add to file: cgame/cg_weapons.c

Code:
----------------------------------------------------------------------

/* [QUARANTINE] - Weapon Animations - CG_ParseWeaponAnimFile
==========================
CG_ParseWeaponAnimFile
==========================
*/
static qboolean CG_ParseWeaponAnimFile( const char *filename, weaponInfo_t *weapon ) {
char *text_p;
int len;
int i;
char *token;
float fps;
int skip;
char text[20000];
fileHandle_t f;
animation_t *animations;

animations = weapon->animations;

// load the file
len = trap_FS_FOpenFile( filename, &f, FS_READ );
if ( len <= 0 ) {
return qfalse;
}
if ( len >= sizeof( text ) - 1 ) {
CG_Printf( "File %s too long\n", filename );
return qfalse;
}
trap_FS_Read( text, len, f );
text[len] = 0;
trap_FS_FCloseFile( f );

// parse the text
text_p = text;
skip = 0; // quite the compiler warning

// read information for each frame
for ( i = 0 ; i < MAX_WEAPON_ANIMATIONS ; i++ ) {
token = COM_Parse( &text_p );
if ( !token ) break;
animations[i].firstFrame = atoi( token );
token = COM_Parse( &text_p );
if ( !token ) break;
animations[i].numFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !token ) break;
animations[i].loopFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !token ) break;
fps = atof( token );
if ( fps == 0 ) fps = 1;
animations[i].frameLerp = 1000 / fps;
animations[i].initialLerp = 1000 / fps;
}
if ( i != MAX_WEAPON_ANIMATIONS ) {
CG_Printf( "Error parsing weapon animation file: %s", filename );
return qfalse;
}

return qtrue;
}
// END

----------------------------------------------------------------------



Example animation.cfg file:

0 20 0 22 // WP_ANIM_READY
20 10 0 22 // WP_ANIM_FIRE

The first number is the starting frame in the model, the second number is the number of frames that animation runs for, the third number is how many frames that animation is to repeat (Ive never needed this so far, legs and such use this in the player animation files) and the third is how many frames to play per second, I would keep this fairly constant as big differences can cause some things to look off.

STEP 5:

All right we have the frames and stuff in but Coyote, we still dont have the model!! Well that is a bit trickier, while you might not want to do this for every weapon, for the purposes of this simple tutorial it is the easiest way. Just below in cg_weapons.c is a function called CG_RegisterWeapon that we need to edit so that the weapon model is added.

Add to file: cgame/cg_weapons.c
Add to function: CG_RegisterWeapon

Code:
----------------------------------------------------------------------

void CG_RegisterWeapon( int weaponNum ) {
weaponInfo_t *weaponInfo;
gitem_t *item, *ammo;
char path[MAX_QPATH];
vec3_t mins, maxs;
// QUARANTINE - Weapon Animations - Added Variable
char filename[MAX_QPATH]; //Used to open animation.cfg files
// END

int i;


if ( ammo->classname && ammo->world_model[0] ) {
weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] );
}

// QUARANTINE - Weapon Animations - Parse animation files

/* This is where you can add support for all weapons, just add
additional cases for other weapons and copy the same code and
just change the file location of the animation.cfg
*/
switch (weaponNum) {
case WP_MACHINEGUN:
Com_sprintf( filename, sizeof(filename),
"models/weapons2/machinegun/animation.cfg" );
if ( !CG_ParseWeaponAnimFile(filename, weaponInfo) ) {
Com_Printf("Failed to load weapon animation file %s\n", filename);
}
break;
case WP_SHOTGUN:
Com_sprintf( filename, sizeof(filename),
"models/weapons2/shotgun/animation.cfg" );
if ( !CG_ParseWeaponAnimFile(filename, weaponInfo) ) {
Com_Printf("Failed to load weapon animation file %s\n", filename);
}
break;
}


strcpy( path, item->world_model[0] );
COM_StripExtension( path, path );
strcat( path, "_hand.md3" );
weaponInfo->handsModel = trap_R_RegisterModel( path );

// QUARANTINE - Weapon Animations - Register models
// Register the animation model into the game
strcpy( path, item->world_model[0] );
COM_StripExtension( path, path );
strcat( path, "_anim.md3" ); // Animations model
weaponInfo->animModel = trap_R_RegisterModel( path );
// END


if ( !weaponInfo->handsModel ) {
weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );
}

// Weapon Animations - So all weapons appear at a single position
weaponInfo->animHandModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );

----------------------------------------------------------------------

STEP 6:

Hold in there, we still have a few more things to cover before we are close to having a working animation system. Ok so we have our models in, we have the frames that we want to use; now we need to be able to advance the frames and display the model on the screen. For this bit of code were going to open cgame/cg_players.c, it is in this file that character animations are handled to and it is so much easier and to me efficient to just use an animation system that is already working, so were going to hook our weapon animation into the actual code that handles character animation. Go down to where it has a big banner that declares PLAYER ANIMATIONS and add in this code.

Add in file: cgame/cg_players.c

Code:
----------------------------------------------------------------------

/* [QUARANTINE] - Weapon Animations
===============
CG_SetWeaponLerpFrame

may include ANIM_TOGGLEBIT
===============
*/
static void CG_SetWeaponLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
animation_t *anim;

lf->animationNumber = newAnimation;
newAnimation &= ~ANIM_TOGGLEBIT;

if ( newAnimation < 0 || newAnimation >= MAX_WEAPON_ANIMATIONS ) {
CG_Error( "Bad weapon animation number: %i", newAnimation );
}

anim = &cg_weapons[cg.snap->ps.weapon].animations[ newAnimation ];

lf->animation = anim;
lf->animationTime = lf->frameTime + anim->initialLerp;

if ( cg_debugAnim.integer ) {
CG_Printf( "Weapon Anim: %i\n", newAnimation );
}
}
// END

----------------------------------------------------------------------

As those that are observant will notice this function is for the most part a copy of the function just below it called CG_SetLerpFrameAnimation, I have simply added un code to access out animation structures and spit out debug code and error code that states the animation is for weapons and not characters.

Now scroll down a bit further and you will notice a function called CG_RunLerpFrame, this function is the meat of the animation system as it takes care of actually advancing through the frames. It is also the function that is called to do it, so we have a issue, how do we tell this function that the animation were calling is going to be a weapon animation and not a character animation? Well there are really quite a lot of ways of doing it but I found the easiest to be to just add in a qboolean parameter and pass in true if it was a weapon animation and false if it was not. Below are the lines of code to change to accomplish this.

Add in file: cgame/cg_players.c
Add to function: CG_RunLerpFrame


Code:
----------------------------------------------------------------------
Before:
static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale) {

After:
static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale, qboolean weaponAnim ) {

----------------------------------------------------------------------


Also need to change the parameters of where it is called
Add in file: cgame/cg_players.c
Add in functions: CG_PlayerAnimation, CG_PlayerFlag


In both of these you need to change the CG_RunLerpFrame parameters with a qfalse at the end of them. There are three calls in CG_PlayerAnimation and one call in CG_PlayerFlag.

Due to some confusion here, the lines of code that must be changed are given below:
Inside CG_PlayerAnimation:
Code:
----------------------------------------------------------------------

// QUARANTINE - Weapon Animation - Added qfalse to indicate a none weapon animation
CG_RunLerpFrame( ci, &cent->pe.legs, LEGS_TURN, speedScale, qfalse );
} else {
CG_RunLerpFrame( ci, &cent->pe.legs, cent->currentState.legsAnim, speedScale, qfalse );
}

*legsOld = cent->pe.legs.oldFrame;
*legs = cent->pe.legs.frame;
*legsBackLerp = cent->pe.legs.backlerp;
// QUARANTINE - Weapon Animation - Added qfalse to indicate a none weapon animation
CG_RunLerpFrame( ci, &cent->pe.torso, cent->currentState.torsoAnim, speedScale, qfalse );

*torsoOld = cent->pe.torso.oldFrame;
*torso = cent->pe.torso.frame;
*torsoBackLerp = cent->pe.torso.backlerp;
}
----------------------------------------------------------------------

Inside of CG_PlayerFlag:
Code:
----------------------------------------------------------------------

// lerp the flag animation frames
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
// QUARANTINE - Weapon Animation - Added qfalse to indicate a none weapon animation
CG_RunLerpFrame( ci, &cent->pe.flag, flagAnim, 1, qfalse );

flag.oldframe = cent->pe.flag.oldFrame;
flag.frame = cent->pe.flag.frame;
flag.backlerp = cent->pe.flag.backlerp;
----------------------------------------------------------------------

Ok now that we have our parameter listing changed so that we can tell it what kind of animation were calling we need to use that parameter to do the work. Change these lines of code.

Add in file: cgame/cg_players.c
Add in function: CG_RunLerpFrame


Code:
----------------------------------------------------------------------

Before:
// see if the animation sequence is switching
if ( newAnimation != lf->animationNumber || !lf->animation ) {
CG_SetLerpFrameAnimation( ci, lf, newAnimation );
}


After:
// QUARANTINE - Weapon Animation
// Check and see if the animation is switching and then check to see if it is a weapon
// animation or character and call the proper lerp frame function
if ( newAnimation != lf->animationNumber || !lf->animation ) {
if (weaponAnim) {
CG_SetWeaponLerpFrame( ci, lf, newAnimation );
} else {
CG_SetLerpFrameAnimation( ci, lf, newAnimation );
}
}
// END

----------------------------------------------------------------------


STEP 7:

Wow, that was a lot of additions in the last step, but dont give up now, were almost there. Ok now we have to actually have a way of calling the CG_RunLerpFrame because those that are observant would have noticed that it is static and Id prefer to keep it that way, also we havent finished with advancing the frames quite yet. While the CG_RunLerpFrame does do a huge amount of work it is the following function that makes it all possible. Scroll down inside of cgame/cg_players.c till you get to the function CG_PlayerAnimation function. Just above that function I want you to add in this code.

Add in file: cgame/cg_players.c

Code:
----------------------------------------------------------------------

/* [QUARANTINE] - Weapon Animations
===============
CG_WeaponAnimation

This is called from cg_weapons.c
===============
*/
void CG_WeaponAnimation( centity_t *cent, int *weaponOld, int *weapon, float *weaponBackLerp ) {
clientInfo_t *ci;
int clientNum;

clientNum = cent->currentState.clientNum;

if ( cg_noPlayerAnims.integer ) {
*weaponOld = *weapon = 0;
return;
}

ci = &cgs.clientinfo[ clientNum ];

CG_RunLerpFrame( ci, &cent->pe.weapon, cent->currentState.generic1, 1, qtrue );

// QUARANTINE - Debug - Animations
#if 0
if(cent->pe.weapon.oldFrame || cent->pe.weapon.frame || cent->pe.weapon.backlerp) {
CG_Printf("weaponOld: %i weaponFrame: %i weaponBack: %i\n",
cent->pe.weapon.oldFrame, cent->pe.weapon.frame, cent->pe.weapon.backlerp);
}
#endif

*weaponOld = cent->pe.weapon.oldFrame;
*weapon = cent->pe.weapon.frame;
*weaponBackLerp = cent->pe.weapon.backlerp;

}
// END

----------------------------------------------------------------------

Add in header: cgame/cg_local.h

Code:
----------------------------------------------------------------------
void CG_WeaponAnimation( centity_t *cent, int *weaponOld, int *weapon, float *weaponBackLerp );
----------------------------------------------------------------------




I went ahead and left in the debug code there for those that might want to be able to take a look at what frames it is calling as it works, be forewarned that this will spew out a lot of text when you animate and is not always helpful but it can be useful.

Another thing you might want to notice about the code above is where it calls CG_RunLerpFrame youll notice we pass in cent->currentState.generic1. generic1 is a variable in the playerState_t and for the most part is only used in the MISSIONPACK parts of the code. For me this is no problem, as I do not use those parts of the code, however for others it might very well be. Just remember that whatever variable you might want to replace it with it has to be one that is transferred between the playerStat_t and the entityStat_t as this is the variable that tells us what animation we currently need to run.


STEP 8:
Now we need to add in the call to CG_WeaponAnimation, which we just made. Inside of cgame/cg_weapons.c there is a single function called CG_AddPlayerWeapon that has a very important refrence in it that makes all this possible. Whenever this function is adding the weapon to the player in third person the ps variable (playerstate for those not in the know) is NULL, but when it is adding the weapon to the first person it is valid so to determine what we need to do when we just add in a few checks for the ps state and then add in our functions.

Add in file: cgame/cg_weapons.c
Add in function: CG_AddPlayerWeapon


Code:
----------------------------------------------------------------------

/*
=============
CG_AddPlayerWeapon

Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
The main player will have this called for BOTH cases, so effects like light and sound should only be done on the world model case.
=============
*/
void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) {
refEntity_t gun;
refEntity_t barrel;
refEntity_t flash;
vec3_t angles;
weapon_t weaponNum;
weaponInfo_t *weapon;
centity_t *nonPredictedCent;
// int col;

weaponNum = cent->currentState.weapon;

CG_RegisterWeapon( weaponNum );
weapon = &cg_weapons[weaponNum];

// add the weapon
memset( &gun, 0, sizeof( gun ) );
VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
gun.shadowPlane = parent->shadowPlane;
gun.renderfx = parent->renderfx;

// set custom shading for railgun refire rate
if ( ps ) {
if ( cg.predictedPlayerState.weapon == WP_RAILGUN
&& cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) {
float f;

f = (float)cg.predictedPlayerState.weaponTime / 1500;
gun.shaderRGBA[1] = 0;
gun.shaderRGBA[0] =
gun.shaderRGBA[2] = 255 * ( 1.0 - f );
} else {
gun.shaderRGBA[0] = 255;
gun.shaderRGBA[1] = 255;
gun.shaderRGBA[2] = 255;
gun.shaderRGBA[3] = 255;
}
}

if ( ps ) {
gun.hModel = weapon->animModel;
}
else {
gun.hModel = weapon->weaponModel;
}


if (!gun.hModel) {
return;
}

if ( !ps ) {
// add weapon ready sound
cent->pe.lightningFiring = qfalse;
if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) {
// lightning gun and guantlet make a different sound when fire is held down
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound );
cent->pe.lightningFiring = qtrue;
} else if ( weapon->readySound ) {
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound );
}
}

if ( !ps ) {
CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon");
}
else {
CG_WeaponAnimation( cent, &gun.oldframe, &gun.frame, &gun.backlerp );
CG_PositionWeaponOnTag( &gun, parent, parent->hModel, "tag_weapon");
}


CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups );


// add the spinning barrel
// QUARANTINE - Animation - To prevent the barrel from showing up in first person
// If you want a spinning barrel in first person, create a new if statement and
// a different tag from what is attached to the third world model.
if ( weapon->barrelModel && !ps ) {

memset( &barrel, 0, sizeof( barrel ) );
VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
barrel.shadowPlane = parent->shadowPlane;
barrel.renderfx = parent->renderfx;

barrel.hModel = weapon->barrelModel;
angles[YAW] = 0;
angles[PITCH] = 0;
angles[ROLL] = CG_MachinegunSpinAngle( cent );
AnglesToAxis( angles, barrel.axis );

CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" );

CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups );
}

// QUARANTINE - Attach the flash to the first person model as well as the external otherwise you
// you get a flash right in the middle of your screen
if (ps)
CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->animModel, "tag_flash");
else
CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash");

----------------------------------------------------------------------

The rest of the function is left alone. Now we need to talk about what we have accomplished here. First youll notice that we have added the additional checks on the ps state, these are not perfect, Im sure if you want to take the time you can make them all go under a single comparison instead of three or four. I myself actually separated the functions into one that handles only third person and another that handles only first person. I simply made it as simple as possible here for the tutorial.

In the first check youll notice we give the gun variable our animation model that we have stored. In the second comparison we advance along our animation by calling our CG_WeaponAnimation and then youll notice that we call a new function CG_PositionWeaponOnTag. This handles the positioning of the model just a bit differently; step 9 shows why we add this function and where to add it and what code to add.


STEP 9:

In step 8 we added in the code that handles drawing the models to the screen and we found a function that we had not yet made called CG_PositionWeaponOnTag. This function is actually just the same as the CG_PositionEntityOnTag except for the removal of a single line of code.


Add in file: cgame/cg_ents.c

Code:
----------------------------------------------------------------------

/* [QUARANTINE] - CG_PositionWeaponOnTag
======================
CG_PositionWeaponOnTag

Changed from CG_PositionEntityOnTag function to prevent backlerp change in animations
======================
*/
void CG_PositionWeaponOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ) {
int i;
orientation_t lerped;

// lerp the tag
trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
1.0 - parent->backlerp, tagName );

// FIXME: allow origin offsets along tag?
VectorCopy( parent->origin, entity->origin );
for ( i = 0 ; i < 3 ; i++ ) {
VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
}

// had to cast away the const to avoid compiler problems...
MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis );
// entity->backlerp = parent->backlerp;
}

----------------------------------------------------------------------

Add in header: cgame/cg_local.h

Code:
----------------------------------------------------------------------
void CG_PositionWeaponOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName );
----------------------------------------------------------------------



As you can see this is identical except for commenting out one line. Why comment out one line of code? Well that is because that one line of code messes with the backlerp frames of the entity being passed in and if we allowed that to happen with our animation model our animations would be off, therefore we had to create this function to prevent that from happening.

STEP 10:

All right stop panting were getting close now. We have all the basics of the underlining system that will do the hard work for us; we now need to add in the functions and the code that will actually tell this code what animation to play. Open up game/bg_pmove.c as this is where most of the animation code for running, and firing in third person is handled it is also the proper place to be handling the animations for our first person stuff. As with other steps lets present the code we need to add and then explain what is happening. Another note on this section of code. Be sure and add it to the beginning of the file, I have had several people email me with errors saying these functions are not defined and it usually is because they added them to the end of the file.

Add in file: game/bg_pmove.c


Code:
----------------------------------------------------------------------
/* [QUARANTINE] - Weapon Animations
===================
PM_StartWeaponAnim, PM_ContinueWeaponAnim
===================
*/
static void PM_StartWeaponAnim( int anim ) {
if ( pm->ps->pm_type >= PM_DEAD ) {
return;
}

pm->ps->generic1 = ( ( pm->ps->generic1 & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
}

static void PM_ContinueWeaponAnim( int anim ) {
if ( ( pm->ps->generic1 & ~ANIM_TOGGLEBIT ) == anim ) {
return;
}

PM_StartWeaponAnim( anim );
}
// END

----------------------------------------------------------------------



Not much code, uh? Well it is with the simplicity of these two functions that were going to handle telling the code what animation to play. Notice, that what were setting with the PM_StartWeaponAnim function is the generic1 variable. Remember us talking about it back in step 7, this is the variable that is checked to see if weve switched animations, as I told you, this is the key to making this work so fluently. Another thing to note is that these functions are identical to the torso animation functions except for the priority animation checking, you have to use up a variable in your playerstate_t to enable this ability and would be of use in reloads or draws if you wished to make sure that those types of animations finished before another animation would play. Now, on to the next step to learn where to place the calls to these functions so as to enable fire animation.

STEP 11:

Well this is the final step, we simple have to place in a few calls to our previous functions to change the animations on the weapons and were finished.

Add in file: game/bg_pmove.c
Add in function: PM_TorsoAnimation


Code:
----------------------------------------------------------------------
static void PM_TorsoAnimation( void ) {
if ( pm->ps->weaponstate == WEAPON_READY ) {
if ( pm->ps->weapon == WP_GAUNTLET ) {
PM_ContinueTorsoAnim( TORSO_STAND2 );
} else {
PM_ContinueTorsoAnim( TORSO_STAND );
}

// QUARANTINE - Weapon Animation
// Should always draw the weapon when it is just ready
PM_ContinueWeaponAnim( WP_ANIM_READY );

return;
}
}

----------------------------------------------------------------------


Add in file: game/bg_pmove.c
Add in function: PM_Weapon


Code:
----------------------------------------------------------------------


if ( pm->ps->weaponstate == WEAPON_RAISING ) {
pm->ps->weaponstate = WEAPON_READY;
if ( pm->ps->weapon == WP_GAUNTLET ) {
PM_StartTorsoAnim( TORSO_STAND2 );
} else {
PM_StartTorsoAnim( TORSO_STAND );
}
// QUARANTINE - Weapon Animation
// Should always draw the weapon when it is just ready
PM_StartWeaponAnim( WP_ANIM_READY );

return;
}

// check for fire
if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) {
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}

// start the animation even if out of ammo
if ( pm->ps->weapon == WP_GAUNTLET ) {
// the guantlet only "fires" when it actually hits something
if ( !pm->gauntletHit ) {
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}
PM_StartTorsoAnim( TORSO_ATTACK2 );
} else {
PM_StartTorsoAnim( TORSO_ATTACK );
}

// QUARANTINE - Weapon animations
// This should change pm->ps->generic1 so we can animate
PM_StartWeaponAnim( WP_ANIM_FIRE );

pm->ps->weaponstate = WEAPON_FIRING;
----------------------------------------------------------------------


As you can see all we are doing is calling our animation functions that we declared at the top of the game/bg_pmove.c file and pass in the enumerated data type that we had declared earlier. Adding in additional animations to this system is as easy as adding in another definition, adding in the frames to a model, changing the animation.cfg of that model and then calling the animation where needed with these same functions, though as you will notice our functions are static which prevents them from being called outside of the bg_pmove.c file.

Well I hope this helps you out in your production of a Quake 3 modification. I have provided this tutorial in the hopes that it will get some people off to a good start and give me the opportunity to play some game that a person might come up with. All I ask is that if you do use this code in your Mod please, drop me an email let me know and give me props for helping you out.

This has been a tutorial by quarantinemod.net
Coyote -Lead Programmer
Quarantine (A Quake 3 Total Conversion)