
Quake DeveLS - Faster Blaster
Author: SumFuka
Difficulty: VERY Easy:)
![]()
Blam blam blam blam blam !
This
tutorial is really simple, but it's something that has been nagging me
lately... how to make your weapons fire more rapidly.
Well the timing of the
weapons is directly related to the animation frames for the weapon models...
the server framerate is 0.1 seconds per frame, or 10 frames per second, so (for
example), the grenade launcher has 12 fire frames, or 1.2 seconds between
shots.
There are two ways to
change your firing rate, either change the server framerate (NOT a good idea...
then quad would only last for 15 seconds, a 20 minute duel might only last 10
minutes in actual time, etc etc...), or to 'hack' the weapon animations.
Well I thought hacking the
weapon animations would be hard but The Yoshi came to the rescue ! Let's
first look at the Weapon_Blaster function in p_weapon.c (line 730) :
void Weapon_Blaster (edict_t *ent){ static int pause_frames[] = {19, 32, 0}; static int fire_frames[] = {5, 0}; Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);}
This
function seems to suggest that the weapon animation frames that are used for
firing/pausing/etc are defined in the code (not in the pak file). We should be
able to 'chop out' some frames to speed up our blaster. Let's look at the
Weapon_Generic function (line 304) to figure out what the 4, 8, 52, 55
numbers mean :
/*================Weapon_Generic A generic function to handle the basics of weapon thinking================*/#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)){ int n; if (ent->client->weaponstate == WEAPON_DROPPING) { if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) { ChangeWeapon (ent); return; } ent->client->ps.gunframe++; return; } if (ent->client->weaponstate == WEAPON_ACTIVATING) { if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) { ent->client->weaponstate = WEAPON_READY; ent->client->ps.gunframe = FRAME_IDLE_FIRST; return; } ent->client->ps.gunframe++; return; } if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) { ent->client->weaponstate = WEAPON_DROPPING; ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; return; } if (ent->client->weaponstate == WEAPON_READY) { if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) { ent->client->latched_buttons &= ~BUTTON_ATTACK; if ((!ent->client->ammo_index) || ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) { ent->client->ps.gunframe = FRAME_FIRE_FIRST; ent->client->weaponstate = WEAPON_FIRING; // start the animation ent->client->anim_priority = ANIM_ATTACK; if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) { ent->s.frame = FRAME_crattak1-1; ent->client->anim_end = FRAME_crattak9; } else { ent->s.frame = FRAME_attack1-1; ent->client->anim_end = FRAME_attack8; } } else { if (level.time >= ent->pain_debounce_time) { gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); ent->pain_debounce_time = level.time + 1; } NoAmmoWeaponChange (ent); } } else { if (ent->client->ps.gunframe == FRAME_IDLE_LAST) { ent->client->ps.gunframe = FRAME_IDLE_FIRST; return; } if (pause_frames) { for (n = 0; pause_frames[n]; n++) { if (ent->client->ps.gunframe == pause_frames[n]) { if (rand()&15) return; } } } ent->client->ps.gunframe++; return; } } if (ent->client->weaponstate == WEAPON_FIRING) { for (n = 0; fire_frames[n]; n++) { if (ent->client->ps.gunframe == fire_frames[n]) { if (ent->client->quad_framenum > level.framenum) gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); fire (ent); break; } } if (!fire_frames[n]) ent->client->ps.gunframe++; if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) ent->client->weaponstate = WEAPON_READY; }}
Wow. This
is a very interesting function. Clean, effecient, easy to read and does some
very nifty stuff. Only a genius like John Carmack could have written a function
so neatly. Also notice the lack of comments. That is the true mark of a
programming prodigy !!! (Don't think that WE can get away with not using
comments though). But, I digress...
The Weapon_Generic
function is called each frame and advances the frames of animation depending on
what you are doing. For example, if you do nothing it simply cycles through the
'idle' frames of animation. If you fire, it goes through the 'fire' animations,
and actually fires at the right time.
Those parameters we were
wondering about, 4, 8, 52, 55, describe the end frames for the various
blaster animations :
Notice
that the idle animation is quite long... and very well done by Paul Steed (the
Quake II character fidgets with his fingers on the gun, and moves it around
slightly...) ! Anyway, let's simply make a change to the Weapon_Blaster
function :
void Weapon_Blaster (edict_t *ent){ static int pause_frames[] = {19, 32, 0}; static int fire_frames[] = {5, 0}; Weapon_Generic (ent, 4, 5, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);}
What did
we do ? We simply changed the last fire animation from frame 8 to frame 5. In
other words we now have 1 weapon fire frame instead of 4... counting the return
to the first idle frame before we fire again, that means we fire every second
frame, or 0.2 seconds. Contrast that with our previous fire Delay of 0.5
seconds. Easy huh ?
Unfortunately the weapon
fire animation looks jumpy. And, the last three weapon fire frames are now
actually part of the 'idle' animations... but, that's the price we pay to get a
Faster, Blaster.
(I told you it would be
less disgusting than the last one... :)
Tutorial by SumFuka
|
This
site, and all content and graphics displayed on it, |