
GreyBear's
Quake 2 Tutorials
Chapter
4: Creating Realistic Weapons Part 3 - More Cool Stuff
Requirements
Contents
Introduction
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.
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! //+BD - NEW CODE BLOCK 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); } //+BD - END NEW CODE BLOCK
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_BULLETS, AMMO_SHELLS, AMMO_ROCKETS, AMMO_GRENADES, AMMO_CELLS, AMMO_SLUGS, //+BD Don't forget to add a comma here... //+BD - New ammo type for flares AMMO_FLARE } ammo_t;
Next,
open up g_items.c and add the following code:
{ "ammo_flare", Pickup_Ammo, NULL, Drop_Ammo, NULL, "misc/am_pkup.wav", "models/items/ammo/bullets/medium/tris.md2", 0,// "models/items/ammo/bullets/9mm/tris.md2", 0, NULL,/* icon */ "a_bullets",/* pickup */ "Flares",/* width */ 3, 50, NULL, IT_AMMO, NULL, AMMO_FLARE,/* 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:
// +BD NEW CODE BLOCK //====================================================================== //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)) { ent-client-ps.gunframe++; return; } ent-client-ps.gunframe++; //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); return; } //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-ps.gunframe=64; 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); else fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,MOD_Mk23); ent-client-Mk23_rds--; } else { //If no reload, fire normally. //+BD - Flare or bullet? if(ent-client-flare = 1) fire_flare(ent, start, forward, 0, 1000, 1, 0, 0); else fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,MOD_Mk23); //+BD and uncomment these two also ent-client-Mk23_rds--; } //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[ammo_index]--; else 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