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, ¢->pe.legs,
LEGS_TURN, speedScale, qfalse );
} else {
CG_RunLerpFrame( ci,
¢->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, ¢->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, ¢->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,
¢->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)