Quake 2 is another classic from id software.While not modded quite as much as it's predecessor or Q3A, it is still a blast to play and the mod's on offer are bundles of fun. Just step into Quake 2 Rocket Arena, armed with the new and almighty rocket and rail gun and you will be gibbing for hours on end.If you dig fast paced action, then load up the good ole Quake 2, fire up a mod or two and have some fun!

Post tutorial Report RSS Understanding Quake2 Weapon Frames

Here I'll show you what all those numbers in weapons mean. Some people find it hard to do. I'll also tell you how to correctly name your model's frames so they work right ingame.

Posted by on - Basic Client Side Coding

[page=Contents]
Ok, We'll be covering alot of ground in this one.

1. How the weapon frames work
2. Naming model frames appropriately
3. Making a simple object that animates

[page=Weapons]
Alright, we'll start with the weapons.

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);
}

See that? It's pretty hard to know what's going on here. We'll take is slow.

First, let's look at the pause_frames. All this means is the idle frames. How they set it up is like this. It starts at 9, goes to 32, and then repeats back to 19. Why doesn't the 9 repeat, and they didn't even mention a 9? I'll get to that later, all I am telling you here is that it repeat once frame 32 is played, and back to 19.

Now the fire_frames. Most people think this is the frames where you attack. They're wrong. Fire_frames is the frame where your fire_ function will take place.

Let's look at this Weapon_Generic. If we check it's code first:

#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))

See how it works there? So, really, that blaster code is saying:

Weapon_Generic (person who's shooting, start fire, end fire, start deactivate, end deactivate, read the pause_frames, read the fire_frames, fire function);
}

Why isn't the idle frames in there? Easy question... Because it's in it's own int!
[page=Frames]
Now let's look at the frames of a model.

The order should be like this:

activate01 - activatexx, fire01 - firexx, idle01 - idlexx, deactivate01 - deactivatexx

if in any other order, Quake2 will read it wrong. Read the Weapon Generic again.

#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))

How it's working is showing that the fire frames is always after the Activate, idle is after the fire, deactivate is after the idle. Simple as that.

[page=Objects]

Now, here's an exeption to the frames. Animated objects always have just one set of frames (if more, it will just play them all anyways). Let's create one together! Open up g_misc.c and go above the

void SP_misc_easterchick

Let's make a tank that will keep doing his smash ground animation; but when it is shot after 40 health, gibs go flying!

so, add this after the misc_easterchick_think

/* misc_tanksmash - By Paril*/

void misc_tanksmash_think (edict_t *self)
{
	if (++self->s.frame < 114)
		self->nextthink = level.time + FRAMETIME;
	else
	{		
		self->s.frame = 76;
		self->nextthink = level.time + FRAMETIME;
	}
}

Easy? Yep!

Now, let's go below that, and add the actual thing, and make it shootable and explode into gibs
First, we add the death mechanism

void tanksmash_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
	vec3_t	vd;
	char	*gibname;

	gibname = "models/objects/gibs/sm_meat/tris.md2";
	gibname = "models/objects/gibs/sm_meat/tris.md2";
	gibname = "models/objects/gibs/sm_meat/tris.md2";

	ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
    ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
    ThrowGib (self, "models/objects/gibs/bone2/tris.md2", damage, GIB_ORGANIC);

	self->s.origin[2] += 32;
	self->s.frame = 0;
	gi.setmodel (self, gibname);
	VectorSet (self->mins, -16, -16, 0);
	VectorSet (self->maxs, 16, 16, 16);

	self->takedamage = DAMAGE_NO;
	self->solid = SOLID_NOT;
	self->s.effects = EF_GIB;
	self->s.sound = 0;
	self->flags |= FL_NO_KNOCKBACK;

	self->movetype = MOVETYPE_BOUNCE;
	VelocityForDamage (damage, vd);
	VectorAdd (self->velocity, vd, self->velocity);
}

void misc_tanksmash (edict_t *ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
                     ent->takedamage = DAMAGE_YES;
                     VectorSet (self->mins, -32, -32, -16);
	VectorSet (self->maxs, 32, 32, 72);
	ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2");
	ent->s.frame = 76;
	ent->think = tanksmash_think;
	ent->nextthink = level.time + 2 * FRAMETIME;
                     ent->health = 40;
                     ent->die = tanksmash_die;
	gi.linkentity (ent);
}

There we go. Now, we've got a fully working "object Tank" that will scroll through his ground smash frames. Now, it's up to you to make it useful; a prop, make it's smash make a shockwave, make it explode when it dies.. or, here's something for you; make it spawn two real tanks out when you kill it ;)

[page=Conclusion]
Hopefully now you understand how frames work a bit better. You can make lots of stuff out of that Weapon_Generic! Two sets of idle frames (before and after attack), random deactivate frames, heck, even a reload code! It's up to you, I just supply you with the basics :):):)

Paril

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: