GreyBear's Quake 2 Tutorials

Chapter 4: Creating Realistic Weapons Part 3 - More Cool Stuff




Howdy! Well, we're close to the finish line on this sucker. In the third and final part of our Realistic Weapons tutorial, we're going to add one more cool effect; the ability to fire more than one type of ammunition from the same weapon. We'll also be exploring how to create and track new ammo types, as well. But first, unless you've been sleeping in class, you'll know that next comes...

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.

Lighting up the darkness

OK, what we're going to do this time is to add the ability of the Mark 23 to fire a flare round. There actually exist special pistol flare rounds, although in all honesty they suck in real life. However, we're going to take a bit of poetic license and allow the Mark 23 to fire a nice big flare. Since my purpose is to teach you how to extend a weapon and not how to create a flare, and since John Rittenhouse has already done the flare bit anyway, I'm going to borrow his flare code from the Qdevels Flare tutorial.

Anyone got a light?

OK, first, let's plop in John's flare code. It's a basic firing function, much like fire_bullet(). For simplicity's sake, to continue id's tradition of self declaring weapons code, let's put the function at the bottom of the g_weapon.c file:

	//+BD - John Rittenhouse's flare firing function. Thanks, John!
	void fire_flare (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float 	damage_radius)
	            edict_t *grenade;
	            vec3_t  dir;
	            vec3_t  forward, right, up;
	            vectoangles (aimdir, dir);
	            AngleVectors (dir, forward, right, up);

	            grenade = G_Spawn();
	            VectorCopy (start, grenade-s.origin);
	            VectorScale (aimdir, speed, grenade-velocity);
	            VectorMA (grenade-velocity, 200 + crandom() * 10.0, up, grenade-velocity);
	            VectorMA (grenade-velocity, crandom() * 10.0, right, grenade-velocity);
	            VectorSet (grenade-avelocity, 300, 300, 300);
	            grenade-movetype = MOVETYPE_BOUNCE;
	            grenade-clipmask = MASK_SHOT;
	            grenade-solid = SOLID_BBOX;
		    //+BD - Here's the magic part. These two lines allow the flare to have a sparking trail,
 		    //+BD - and a green glow around the flare itself.
	            grenade-s.effects |= EF_BLASTER;
	            grenade-s.renderfx |= RF_SHELL_GREEN;
	            VectorClear (grenade-mins);
	            VectorClear (grenade-maxs);
	            grenade-s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
	            grenade-owner = self;
	            grenade-touch = Grenade_Touch; 
	            grenade-nextthink = level.time + timer + 35;
	            //+BD - Changed to die to make things simpler and get rid of the explosion
			grenade-think = Grenade_Die; 
	            grenade-dmg = damage;
	            grenade-dmg_radius = damage_radius;
	            grenade-classname = "grenade";
	            // CCH: a few more attributes to let the grenade 'die'
	            VectorSet(grenade-mins, -3, -3, 0);
	            VectorSet(grenade-maxs, 3, 3, 6);
	            grenade-mass = 2;
	            grenade-health = 10;
	            grenade-die = Grenade_Die;
	            grenade-takedamage = DAMAGE_YES;
	            grenade-monsterinfo.aiflags = AI_NOSTEP;
	            gi.linkentity (grenade);

Adding new ammo types

We need to create a new ammo type for our flare before we can fire it. There are two places we need to make changes, on in g_local.h, and one in g_items.c. As before, because of the nature of the code, I'm making changes in existing code rather than segregating it in new code modules. Before you whine and point to the philosophy above, remember; "Consistency is the hobgoblin of little minds". Nyah. Crack open the g_local.h file and add the following code:

	typedef enum
  	AMMO_SLUGS,    //+BD Don't forget to add a comma here...
  	//+BD - New ammo type for flares
  	} ammo_t;

Next, open up g_items.c and add the following code:

		"models/items/ammo/bullets/medium/tris.md2", 0,
//		"models/items/ammo/bullets/9mm/tris.md2", 0,
/* icon */	"a_bullets",
/* pickup */	"Flares",
/* width */	3,
/* precache */	""

Explanation: This entry defines the characteristics of the ammo type we'll use. Since I'm no modeler or artist, we'll use the icons for bullets. Note that the ammo model for bullets will be used also, so it'll be a surprise as to which ammo type you'll get when picking up ammo for the pistol!

Extra Credit: Create the icon and models for a totally new flare ammo type.

Wiring it up

We need to revisit our Pistol firing code once again, this time to add new code to allow us to fire either a bullet or a flare. We'll also need to decrement the count of the ammo type we've fired. Since id never implemented the idea of a weapon that could fire more than one type of ammo, we'll need to work a a little magic to get to each type of ammo, but hey, that's what makes life fun, huh? Here's the code:

	//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;	
		//+BD - We use these here to get access to flare ammo
		int			ammo_index;
		gitem_t		*ammo_item;
		//Find the flares, and use these vars to access the flare inventory
		ammo_item = FindItem("Flares");
		ammo_index = ITEM_INDEX(ammo_item);

		//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!
		//+BD - Add Check for flare ammo now too...
		if (ent-client-pers.inventory[ent-client-ammo_index] <1 || ent->client-pers.inventory[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...
		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;
			//+BD - Flare or bullet?
			if(ent-client-flare = 1)
				fire_flare(ent, start, forward, 0, 1000, 1, 0, 0);
				fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,MOD_Mk23);
			//If no reload, fire normally.
			//+BD - Flare or bullet?
			if(ent-client-flare = 1)
				fire_flare(ent, start, forward, 0, 1000, 1, 0, 0);
				fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,MOD_Mk23);

			//+BD and uncomment these two also
		//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. 
		//+BD - Deplete the right ammo type...
		if(ent-client-flare = 1)
	 		ent-client-pers.inventory[ent-client-ammo_index] -= ent-client-pers.weapon-quantity;	

Explanation: We modified the Pistol firing function to check for the new flare ammo, as well as deplete the flare count if a flare is fired. We used a new variable as you may have noticed. Of course, we still need to declare it in the right place and devise a place to set it and to get the player's command to use it. Since I covered adding a new user function way back in tutorial one, and by now you should be familiar enough with the ent edict to figure out where the flare variable is declared, I'm going to leave that up to you to implement. Here's your chance to see whether any of this stuff is sinking in.

Extra Credit:

1. Add a new sound for the pistol to make if firing a flare. Hint: Use the gi.sound line below the firing sequence as a template.

2. Use the HUD to display the number of flare rounds next to the bullets left for the pistol. Hint: Take a look at g_spawn.c for the macro structure of the HUD display.

The Result

Well, you should now have a pistol that fires either flares or bullets, and decrements the ammo count as the rounds are fired. Of course, this opens up all kinds of possible weapons, as well as the ability to implement things like alternate firing modes for weapons, ala UnReal or Half Life. It also allows you to model realistic weapons like submachineguns, that have several firing modes.

Take a look at the flare firing command, and you'll see that I've set the flare firing attributes by hand. You might want to fiddle with these to suit yourself.

Hope you enjoyed this series as much as I enjoyed writing it.

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