|
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: 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! Step 1 Step
2 /*************************************************************************** RotatePointAroundVector(ent->velocity,
ent->pos1, ent->velocity, 30); /*The velocity
vector must be /***********
fire_rocket2 ******************************************** /***********
Weapon_RocketLauncher_Fire ********************** void
Weapon_RocketLauncher_Fire (edict_t *ent) 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; 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 VectorCopy (start,
rocket->pos2); /*Added: Save the start position of the
rocket (Not sure if 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; 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.
Intro
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
Tutorial
Code
Open your quake2 project
(or workspace or whatever)
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*/
rotated also*/
ent->nextthink = level.time + .1; //Need to think
again in .1 seconds
}
* 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
* 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);
{
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
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);
the axis around which the other two rockets rotate*/
this is necessary. This info might already be somewhere
else)?*/
rocket->nextthink = level.time + .1;