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,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4 or IE 3