GreyBear's Quake 2 Tutorials

Chapter 4: Creating Realistic Weapons Part 1 - Creation


Contents Introduction

Welcome back! I've had several requests for weapons related tutorials lately. Since weapons coding is so difficult to separate out into specific categories (adding, reload, multifunction, etc.), and since I'm endowed with at least one of the five virtues of a good programmer (laziness. Read Programming Perl from O'Reilly & Associates for the other 4), I'm going to handle this by creating one longer tutorial covering specific topics in a multipart form. What this means to you, dear browser, is that you'll need to work along with me through all the parts to get the entire picture.

In this tutorial, we'll go through the process of creating a totally new type of weapon for Quake2. We'll add several features to it as we go, with the objective of teaching you how to prototype and create your own weapons features. Before we begin, the standard philosophy lesson:

GreyBear's Quake 2 programming philosophy

My philosophy about making code changes is this:

Make your changes in your own code files whenever possible. That means for our mods, we'll be adding NEW files to the Quake 2 DLL, not merely inserting our mods into the original files. Why? Well, two reasons. One, it's much easier to find your mods if you keep them in your own files, named by you with descriptive names that you can remember. Two, it maintains the integrity of the original code. As programmers we should respect the work of others. Rather than just hack it up, we should add to it gracefully, and clearly mark our additions as our own.

 When we can't follow the above, as in modifying global include files, or modifying global structs, we will segregate our mods in the code, and clearly mark them as our additions. I also highly recommend you use a consistent marker, like your initials. That way, you can search the code for your initials and easily find every mod you make in id code. Again, it makes for easy maintenance.

NOTE: In the body of the tutorial text, code modifications made by us will have a + sign in a comment field along with my initials, to clue you in that the line was added to the original surrounding code.

Weapons = Items

As the title suggests, a weapon is an instance of a Quake 2 item. It's NOT an entity. What's the difference? Plenty.

An entity (a player or a monster, for example) is individually unique. What happens to one, does not necessarily happen to all, and they don't all share the same set of properties. An item, however, is not individually unique. There can be dozens of a certain item in a game, but ALL of those items share exactly the same properties. This makes it easier to create and keep track of, but somewhat harder to customize. For a personally embarrassing example of what happens when you forget this distinction, see my Sabotage tutorial. I failed to read far enough into the code to discern that items were carbon copies of each other before coming up with what was an otherwise nifty idea. As a consequence, when you sabotaged one weapon using my code, you sabotaged ALL weapons of that type in the game! That's cool if it's what you intend. Unfortunately, it wasn't what I intended.

Creating a new weapon

OK, with a few preliminaries out of the way, let's get down to it. We're going to create a new weapon type in Quake2. In keeping with my recent theme of drawing upon my current work, we're going to create a realistic weapon from the present, and one that appears in my current project, Navy Seals: DevGroup (previously code named Navy Seals 2). We're going to create a Mark 23 Semi automatic pistol. First we'll create the weapon. Then, in the next installment, we'll add the ability to reload it, keeping track of the number of rounds in a clip, and the number of clips in the player's possession. Finally, we'll do something goofy and add the ability for the pistol to shoot flares.

Well, let's get coding then. The first thing to do is crack open g_items.c and add our new weapon to the itemlist[] array. When you open g_items.c, look at the top for a forward declaration of the weapons and add the following line:

	 void Weapon_Pistol(edict_t *ent);
This merely tells the compiler about the function and the value of the passed arguments.

Next, drop to the itemlist[] array. Find the last of the standard Quake2 weapons entries (the BFG is fine), and add the following:

	/* +BD 2/7 - Our SOCOM Mk23 Pistol. This will replace the blaster as the standard sidearm
	*   itemlist[6]

			"w_mk23", //we need an icon for the Mk23
I'm going to assume that we've been together long enough that you have a grasp of what the gitem_t struct entries mean. If not, take a moment and look here. This reference contains all the info you'll need about entities and items structures, and what each variable is for. Note, however, that the second and fourth fields in this declaration are NULL. These are the pickup and drop functions, respectively. They tell the game how to handle things when a player picks up or drops a weapon of this type. The Mark 23 was designed to replace the Blaster in our TC, so these don't apply. If you want to make this weapon available for pickup and discarding by players, merely replace the NULLs with Pickup_Weapon and Drop_weapon to make them visible and usable in the Quake 2 world.

Explanation: What we've done is insert a new weapon type into the itemlist. At map loading time, the game's spawning function parses the map file, and when it runs into an item to be spawned, iterates through the itemlist[] struct to find instructions for how to spawn that item. What we've just added is the list of instructions for how to spawn the item, and how it should behave in the Quake2 universe.

Adding functionality Now that the weapon actually exists in the Quake2 universe, we need to add the functions that are specific to our new weapon in order for it to function. For the time being, we're going to add the ability to fire bullets. Then we'll get reloading implemented. Later we'll add the code to fire flares, as a demonstration of weapons that have multiple functions.

OK, open up p_weapon.c. We're adding it here an not within our own code file because of the need to get at Weapon_Generic() and have access to some #defines that are local to that function. First is our firing function, Pistol_Fire(). You can place this code at the end of the file with a comment bar marking this code as your own:

	//Mk23 Pistol - Ready for testing - Just need to replace the blaster anim with 
	//the correct animation for the Mk23.
	void Pistol_Fire(edict_t *ent)
		int	i;
		vec3_t		start;
		vec3_t		forward, right;
		vec3_t		angles;
		int		damage = 15;
		int		kick = 30;
		vec3_t		offset;	

		//If the user isn't pressing the attack button, advance the frame and go away....
		if (!(ent->client->buttons & BUTTON_ATTACK))

		//Oops! Out of ammo!
		if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
			ent->client->ps.gunframe = 6;
			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;
			//Make the user change weapons MANUALLY!
			//NoAmmoWeaponChange (ent);

		//Hmm... Do we want quad damage at all in NS2?
		//No, but if you do, uncomment the following 5 lines 
		//if (is_quad)
		//	damage *= 4;
		//	kick *= 4;
		//Calculate the kick angles
		for (i=1 ; i<3 ; i++)
			ent->client->kick_origin[i] = crandom() * 0.35;
			ent->client->kick_angles[i] = crandom() * 0.7;
		ent->client->kick_origin[0] = crandom() * 0.35;
		ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;

		// get start / end positions
		VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
		AngleVectors (angles, forward, right, NULL);
		VectorSet(offset, 0, 8, ent->viewheight-8);
		P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);	

		//BD 3/4 - Added to animate last round firing...
		// Don't worry about this now. We'll come back to it later.
		//if (ent->client->pers.inventory[ent->client->ammo_index] == 1 || (ent->client->Mk23_rds == 1))
			//Hard coded for reload only.
			//ent->client->weaponstate = WEAPON_END_MAG;
			//fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,MOD_Mk23);
			//If no reload, fire normally.
			fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,MOD_Mk23);
		//BD - Use our firing sound
		gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/mk23fire.wav"), 1, ATTN_NORM, 0);

		//Display the yellow muzzleflash light effect
		gi.WriteByte (svc_muzzleflash);
		gi.WriteShort (ent-g_edicts);
		//If not silenced, play a shot sound for everyone else
		gi.WriteByte (MZ_MACHINEGUN | is_silenced);
		gi.multicast (ent->s.origin, MULTICAST_PVS);
		PlayerNoise(ent, start, PNOISE_WEAPON);

		//Ammo depletion here. 
		ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;	
Explanation: What we've done is added the function that executes every time the user wishes to fire the weapon. It sets up the amount of firing error (the kick_* values), figures out where the bullet should originate from (the Vector calculations),and then decides what animation to play based on whether we're down to the last round or not. Since we're not worried about how reload works quite yet, just accept that this code works for now. We'll explain why in a bit. Then, the code calls the standard Quake2 function fire_bullet() to actually shoot the projectile and figure out the hit/damage result. The code then creates the proper sound and lighting effects based on whether the pistol is silenced or not. Note that this code fragment uses a custom sound of our own rather than playing a standard Quake2 sound. So now you know how! Also, you can precache the sound in the item declaration, but this doesn't always work. Apparently Quake2 caches some sounds at run time (we think this is hard coded into the engine), and tends to play these standard sounds instead of the ones you cache. So, the above code overrides this tendency.

Finally, the ammo count is decreased to keep things honest. Also note two other things that contribute to realism. The quad damage check is commented out. A real weapon always does real damage. However, I left the code in place so you can see where it would go if you decided you'd like to keep it. I have also commented out the automatic weapon change when the player runs out of ammo. If you like this, you can uncomment this as well.

Next, we need to wire the Pistol_Fire() function to id's generic weapon function, called appropriately enough, Weapon_Generic(). This relieves you from having to code animations and actions for every weapon. Add it directly after Pistol_Fire(). Here's the code. As you can see, it's pretty simple:

	void Weapon_Pistol (edict_t *ent)
		//Idle animation entry points - These make the fidgeting look more random
		static int	pause_frames[]	= {13, 32, 42};
		//The frames at which the weapon will fire
		static int	fire_frames[]	= {10, 0};

			//The call is made...
		Weapon_Generic (ent, 9, 14, 39, 42, /* Reload. Wait for it.63, 68,*/ pause_frames, fire_frames, Pistol_Fire);

Explanation: This function is basically a bridge between your firing function and the generic id weapon function. Note the division of labor here; you provide the weapon's functionality, and id provides the common display and checking code. A pretty nice way of doing things.

Interesting trivia tidbit: Note that the Pistol_Fire() function is never declared anywhere before it's use, as we did with Weapon_Pistol, at the top of p_weapon.c. Why is that? Well, because the function itself was placed right before it's only call in Weapon_Pistol(). In effect, this made the function self declaring! This is the way id did all of the weapons functions, and this is why you need to keep the functions in a certain order, unless you add declarations to the top of the file, or to a header.

All we're really doing here is defining where we're willing to let the display function start playing idle animations, and where we want the firing to occur to make it look realistic. Then, we merely call the Weapon_Generic() function with the correct parameters. But wait, what are all those goofy numbers between the entity variable and the frame variables we just declared? Well let's look at the declaration for Weapon_Generic(), as we're going to be modifying it later anyway:

		void Weapon_Generic (	edict_t *ent, 
					int FRAME_FIRE_LAST, 
					int FRAME_IDLE_LAST, 
					/*+BD added for reload. 
					We'll uncomment these pretty soon
					int *pause_frames, 
					int *fire_frames,
					 void (*fire)(edict_t *ent))

As you can see now, the variables are pretty self explanatory. This is where #defines make your life easier. The numbers are the values of the frames that end the animations we care about playing. We'll get to activating the reload sequences in the next part of our journey. For now, just realize that this is what these numbers are.

Now, this brings up another question, namely, if we just pass the Weapon_Generic() function the end frames, how does it know where to start? Here's the answer. These defines are located right above the Weapon_Generic function code:


Why is this important? Well, if you are having a modeler create models for you, it tells you that he needs to sequence the frames in a particular order. The order is:

This is also important because it lets you know that the easiest place to add new animations is at the end of the animation frames, and that you will need to code for this.

Finally, we need to do one other thing. Tell the game about our MOD (Means of Death) tag. We need to add a new define to g_local.h to let the code know about getting killed with our new weapon. The code looks like this


	//+BD - Added for firing. Tells server how you died?
	#define MOD_Mk23			   34

	#define MOD_FRIENDLY_FIRE	0x8000000

And then, add a blurb into p_client.c, with some smart ass comment that prints when the player gets potted with this particular weapon. Look for the section that sets the attacker. It's pretty hard to kill yourself with the Mark 23 in Quake2, so I didn't add one here. You certainly could if you like. Here's the code:

	self->enemy = attacker;
	if (attacker && attacker->client)
		switch (mod)
	//+BD Add our client obit messages
	case MOD_Mk23:		
	message = "was ventilated by";
	message2= "'s Mark 23 Pistol";
	//+BD end of add
	message = "was blasted by";

Explanation: This part of the code is a 3.14 era addition from id. It allows you to customize the player obits as in Quake 1. We merely added the obits we want to display when someone gets killed by our new weapon.

Stuff you'll need to get

To make your life easier, I've created a zip file containing the weapon model/skin, and sounds for the Mark 23. These are the same models we're using in Navy Seals: Dev Group. I've created the zip file with relative directory information in them, so all you need to do is extract them into your baseq2 folder within the Quake2 directory to use them in the game. You can also put them in a custom subdirectory if you've already set Quake2 up that way.

PLEASE READ: The zip file contains proprietary materials which are the property of Dream Park, developers of Navy Seals: DevGroup. By downloading this zip file, you implicitly agree not to sell, redistribute or use any contents of this file, except for your own personal use. The contents of this zip file MAY NOT be used in Quake 2 modifications that are released to any third parties.

Please, don't make me come looking for you. I'm providing this stuff for your ease of learning. Don't screw it up by using it in ways you shouldn't. Thanks.

If you agree to the above, go ahead and download the zip file here.

The result

OK, that pretty well wraps up Part I. We've created a new weapon by inserting a declaration for it in the itemlist[], and written new weapons functions for it. If you like, you can make this weapon the default weapon, replacing the blaster. All you need do is open up p_client.c and look for the line:

		item = FindItem("Blaster");

and replace it with:

		item = FindItem("Mk23");

Voila! You now will start up with your new pistol in hand! Of course, you might want to give yourself some ammo too... Well, that's the extra credit this time!

Until next time, then... ENJOY!

Contacting the Author

Questions? Problems? Write me, and I'll try to answer your question or help you with debugging. Send your queries to me here.

Tutorial written by GreyBear