Created By: Tim Matthews
eMail t.n.matthews@mail.utexas.edu
Difficulty Scale Easy

Intro

First of all, I would like to take this opportunity to agree completely with what dmgoober wrote. It is a bad idea to download random DLL's. You can be sure that someone will write a malicious Quake2 DLL, and you don't want that DLL on your system!

This is a small demo that makes your rocket launcher fire three rockets at a time. Also, the two outer rockets rotate around the center rocket as the rockets fly away from you. It demonstrates how to make quake entities think over a period of time. Sorry if this article is a little long.

You will need:
Visual C++ 4.0 or above (but I'm sure you could get it working with Watcom, Symantic, etc)
Quake 2 Source Files. 
(This is available at: ftp://ftp.idsoftware.com/idstuff/quake2/source/q2source_12_11.zip) 

 

General Setup 

You will, of course, need to have you source code set up and ready to go. If you haven't done so, see dmgoober's first Tutorial. One addition, though. If you want to avoid all that DLL copying everytime you recompile and you have MSVC 4.0 or above, perform the following:

1. Go to the Project menu and choose Settings... (or hit Alt-F7)

2. The Project Settings dialog box will come up. Click on the Link tab.

3. In the upper left hand corner of the box, there is a combo box labeled "Settings for:". Change the combo box to All Configurations.

4. In the Output file name: field, type "f:\Quake2\mygame\gamex86.dll" (Of course, you will have to change the path to wherever your quake game directory is).

That's it. Now, on with the tutorial!

   
Tutorial Code

Step 1
Open your quake2 project (or workspace or whatever)

Step 2
Open the "q_weapon.c" file. 

Step 3

Paste the following code into the file q_weapon.c

/***************************************************************************
*Spiral Rockets MOD *
***************************************************************************/

/***********Update Rocket Position every .1 seconds**************/
static void mod_SpiralThink(edict_t *ent)
{
vec3_t newpos, pos;

VectorSubtract(ent->s.origin, ent->pos2, pos); /*translate current position*/
RotatePointAroundVector(newpos, ent->pos1, pos, 30); /*So we can rotate around our original line
of sight*/
VectorAdd(ent->pos2, newpos, ent->s.origin); /*then translate the current position back*/

RotatePointAroundVector(ent->velocity, ent->pos1, ent->velocity, 30); /*The velocity vector must be
rotated also*/

ent->nextthink = level.time + .1; //Need to think again in .1 seconds
}

/*********** fire_rocket2 ********************************************
* This procedure is basically a carbon copy of fire_rocket(), except for a
* few variable initializations. Also, the line of sight of the center rocket
* is passed in and saved
***************************************************************************/
void mod_fire_rocket2(edict_t *self, vec3_t start, vec3_t dir, vec3_t los, int damage, int speed,
float damage_radius, int radius_damage)
{
edict_t *rocket;

rocket = G_Spawn();

VectorCopy (los, rocket->pos1); /*Added: Save the line of sight of the center rocket. This is
the axis around which the other two rockets rotate*/
VectorCopy (start, rocket->pos2); /*Added: Save the start position of the rocket (Not sure if
this is necessary. This info might already be somewhere else)?*/

VectorCopy (start, rocket->s.origin);
VectorCopy (dir, rocket->movedir);
vectoangles (dir, rocket->s.angles);
VectorScale (dir, speed, rocket->velocity);
rocket->movetype = MOVETYPE_FLYMISSILE;
rocket->clipmask = MASK_SHOT;
rocket->solid = SOLID_BBOX;
rocket->s.effects |= EF_ROCKET;
VectorClear (rocket->mins);
VectorClear (rocket->maxs);
rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
rocket->owner = self;
rocket->touch = rocket_touch;
rocket->nextthink = level.time + .1;
rocket->think = mod_SpiralThink;
rocket->dmg = damage;
rocket->radius_dmg = radius_damage;
rocket->dmg_radius = damage_radius;
rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");

if (self->client)
check_dodge (self, rocket->s.origin, dir, speed);

gi.linkentity (rocket);
}

Step 4

Open the file p_weapon.c
Step 5

You will need to find the Weapon_RocketLauncher_Fire (edict_t *ent) procedure. Replace it with the following code there

/*********** Weapon_RocketLauncher_Fire **********************
* Modified to launch three rockets, two of which rotate in a spiral
* pattern around the center rocket.
******************************************************************/

/*forward declaration for fire_rocket2*/
void mod_fire_rocket2(edict_t *self, vec3_t start, vec3_t dir, vec3_t los, int damage, int speed,
float damage_radius, int radius_damage);

void Weapon_RocketLauncher_Fire (edict_t *ent)
{
vec3_t offset, start;
vec3_t forward, right;
vec3_t traj_angle, lineofsight; /*Added: Trajectory angle of rocket and line of sight for the center rocket*/
int startspeed; /*Added: Start speed of the rockets*/

int damage;
float damage_radius;
int radius_damage;

startspeed=600; /*Added: Set the rocket speed to taste*/

damage = 100 + (int)(random() * 20.0);
radius_damage = 120;
damage_radius = 120;
if (is_quad)
{
damage *= 4;
radius_damage *= 4;
}

AngleVectors (ent->client->v_angle, forward, right, NULL);

VectorScale (forward, -2, ent->client->kick_origin);
ent->client->kick_angles[0] = -1;

/*Added: Start of modified launcher code*/
VectorCopy(forward, lineofsight); /*our line of sight is the same as our forward view vector*/
VectorCopy(ent->client->v_angle, traj_angle); /*get the trajectory angles for later modification*/

/*This is the original launch code*/
VectorSet(offset, 8, 8, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
mod_fire_rocket2 (ent, start, forward, lineofsight, damage, startspeed, damage_radius, radius_damage);

/*Added: Now, modify the trajectory of the second rocket to 5 degrees off of the center axis*/
traj_angle[1] = ent->client->v_angle[1]+5;
AngleVectors (traj_angle, forward, right, NULL);

VectorSet(offset, 8, 8, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
mod_fire_rocket2 (ent, start, forward, lineofsight, damage, startspeed, damage_radius, radius_damage);

/*Added: Now, modify the trajectory again, 5 degrees in the other direction*/
traj_angle[1] = ent->client->v_angle[1]-5;
AngleVectors (traj_angle, forward, right, NULL);

VectorSet(offset, 8, 8, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
mod_fire_rocket2 (ent, start, forward, lineofsight, damage, startspeed, damage_radius, radius_damage);
//Added: End modified launher code


// send muzzle flash
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
gi.WriteByte (MZ_ROCKET | is_silenced);
gi.multicast (ent->s.origin, MULTICAST_PVS);

ent->client->ps.gunframe++;

PlayerNoise(ent, start, PNOISE_WEAPON);

/*enable this if you want your rocket supply to be updated*/
/*ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;*/
}

Step 6

Notice the forward declaration before the above procedure. It must be there so the compiler knows that the function exists elsewhere.
Step 7

If you have set quake up to run as suggested by dmgoober, then quake will start up with your DLL and in

deathmatch mode. So, simply and find a rocket launcher and shoot at something to see the rockets spiral.
   
Ending

That should be everything, although I can't be sure because my source code has been modified since (I'm working on my next mod). If there is a problem, let me know and I'll post the fix.

Okay, so what did I do? Well, the comments should help explain things a lot. Regardless, let's start with the modification to the function Weapon_RocketLauncher_Fire(). The first thing I did was add a few new variables. The variable traj_angle is the angle of the rocket's trajectory with respect to the point from which the rockets are fired. When shooting rockets, this angle is the same as your client->s_vangle variable. (Don't worry if you don't know what that variable is, it just contains information about your viewing direction). This client->s_vangle variable is copied to traj_angle later because it will be modified. The lineofsight variable is just the axis around which the second and third rockets will rotate. It is the line of sight of the center rocket. And finally, the startspeed variable just contains the speed of the rockets.The code is pretty straight forward. The lines:

traj_angle[1] = ent->client->v_angle[1]+5;
AngleVectors (traj_angle, forward, right, NULL);

VectorSet(offset, 8, 8, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
mod_fire_rocket2 (ent, start, forward, lineofsight, damage, startspeed, damage_radius, radius_damage);

Where added in order to launch the second rocket. Then, the same code is repeated, except the first line of this code snippet is changed to

traj_angle[1] = ent->client->v_angle[1]-5; /*five degrees to the left, this time*/

This fires the second rocket 5 degrees to the left, instead of five degrees to the right like the first statement.That's about it for this procedure.

Now, let's take a look at the mod_fire_rocket2() procedure. This procedure is an exact copy of fire_rocket(), except it adds the following lines:

VectorCopy (los, rocket->pos1); /*Added: Save the line of sight of the center rocket. This is
the axis around which the other two rockets rotate*/

VectorCopy (start, rocket->pos2); /*Added: Save the start position of the rocket (Not sure if
this is necessary. This info might already be somewhere else)?*/

These lines just copy the line of sight and start position into the pos1 and pos2 fields, respectively (I'm not sure what these fields are used for, but doing this doesn't seem to affect the rocket's behavior at all, so I used them). The reason a whole new copy of fire_rocket() had to be made is because the line of site variable had to be passed into the function.

Also, the rocket->think and rocket->nextthink fields had to be changed to:

rocket->think = mod_SpiralThink;
rocket->nextthink = level.time + .1;

This is because our rocket needs to "think" every so often. (Every .1 seconds to be exact)

Okay, now the most interesting function, the mod_SpiralThink() function. This is the function that was specified to do the thinking for the rocket. All it does, basically, is rotate the rockets around the line of site (which is stored in the pos1 field). It also rotates the rockets' velocity vector, so they will continue flying in the correct direction. The function uses the extremely useful RotatePointAroundVector() function. This function rotates a point around a given axis of rotation. In order to rotate correctly, the positions of the rockets relative to their starting points must be used. That is the reason for the VectorSubtract and the VectorAdd functions preceding and following the call to RotatePointAroundVector(). The velocity vector does not need to be translated, so RotatePointAroundVector() is simply called to do the rotation. The last thing the function does is update the ent->nextthink field.This is necessary because we need to indicate to the quake engine that this entity needs to think again. (In .1 seconds)

Well, that's it. If I've accidentally left out some code that keeps this code from compiling, or that makes it work incorrectly, please let me know and I'll fix it.

 

Tutorial html coding modified by legion.


If it is created, then it is copyrighted. Quake 4 Tutorial #1 is (c)1997-98 by Tim Matthews
and the Inside3D staff. The site is hosted by the one and only TeleFragged. Please direct any
flames, comments, or praises to the author. Any and all information found in this tutorial may
be used in any Quake modification provided that the author and the Inside3D staff are credited.