The original article can be found here
Introduction
In this article I have shown how to compile and modify the Quake 3 source code to create a Homing Missile. This article is intended for beginners.
Background
Quake III was released few years back (somewhere in 2001-2002 if I am not wrong). At the same time, I was looking forward to buy a new PC and while researching for the same, I got a computer magazine with a Quake III demo. That's when I decided that Quake III was going to be my benchmark for the new PC. Excellent game and my favorite till date. Now let me stop telling stories and get back to the point.
Source code
Quake3's source code was launched some time after its release. The code does not have a hell lot of comments, but its very readable and understandable. You must know C and a bit of 3D vector mathematics. Mathematics I suppose can be learnt in case you don't have that background. At times you might get stuck in understanding the code due to vector operations performed. Just take a pen and paper and calculate it down, life will be much easier with those two objects.
The entire source code has not been released. Though I don't know which part, a major part like the rendering engine could be under cover.
Quake3's 3D engine has been proven as one of the best 3D engines made and some people have also said that nobody can write better code than the one written for this engine. Recently I also had a chance to go through the Microsoft Techfest, in which, one of the demonstrated research project was based on Quake III. FYI: It was regarding improving multiplayer gaming performance over a low bandwidth network around 100kbps. So, there is a lot of potential in reusing its code if your research/application requirements fit the same.
NOTE: Quake 3's source code is free does not mean that it's game data is free. And you cannot play the game without the game data (maps, sounds, music, bot models, etc.)That's why you still need to buy an original Quake 3 CD.
Why am I re-writing this?
You will find many websites with bits and pieces of information for compiling and modifying Quake 3 source. Inadequate or incorrect information leads to frustration most of the time as it was the case with me. It took a lot of time for me to finally compile and run quake 3 from the compiled code. Creating mods (relatively means modifying source) is another story, but that was fun. The code-change illustrated here again took a lot of time. Understanding and tweaking the code given here is also a good way of learning some basics about this game.
Compiling the source code
You won't find the entire source code here, but you can download it from Id software's website.
Attached are the source files for the modifications. You can also see the compiled Mods here.
Before you start, it is also important to check the following:
- You have a full version of Quake 3 already installed. (Demo version won't work for us)
- Download Quake 3 point release 1.32 from the website and install it to your Quake 3 game directory.
- Check that you have Visual Studio 2003 installed on your machine. If you don't have VS2k3 and use VC++6.0, life's a bit difficult. Please see if this helps.
Now follow the steps given below to compile the source code.
Now to run the game with your compiled code, just double-click the batch file, and you are ready to play. An important step has been covered by this time.
NOTE: Debugging is (as usual) possible in Quake 3 too. That is not covered in this article.
NOTE: Actually you don't need to change the EXE until you want some changes in the quake3 EXE project code. But I recommend this since I have noticed that sometimes the original EXE takes a lot of time to load when we implement our modifications.
WARNING: While making your modifications it is recommended to run Quake 3 in windowed mode (and not the normal full screen mode). You can switch to windowed mode by the pressing Alt+Enter. Quake 3 remembers the configuration (atleast on my PC) while loading the next time. Runtime errors may hang up your inputs in full screen mode and thereby force you to restart without proper shutdown.
Before you modify the code (some basics)
It is important to understand some concepts of Quake 3 before you modify the code. Here I am defining only a very small part required to understand the code illustrated here. There are lots of things that you will find out once you have started doing more than what's presented here.
Anything that is created in the game is called an entity. Every entity created in Quake 3 has an associated think
function. The think
function defines the kind of behavior the entity will exhibit at various times in the game.
For example if you see the fire_rocket
method in g_missile.c under the game project (under quake3.sln), you will find lines stating:
bolt->nextthink = level.time + 15000;
bolt->think = G_ExplodeMissile;
G_ExplodeMissle
is a think function for the rocket. This means that after 15 seconds the rocket will explode. Let us call this time frame as the Next Think time.
We can have our own think
function that will define what the rocket will do after a time specified. By default a missile entity doesn't require processing once released, but depending on the entity type and its behavior, the next think time can be different.
The Homing Missile
By definition, the homing missile is a kind of missile which when fired will find a nearest target and steer itself towards it and finally blast into the target.
In Quake 3, we will enable firing of homing missile from the rocket launcher itself. A player can select whether he wants to fire a simple rocket or homing missile (same rocket behaving differently).
We can divide this task into two parts:
- Taking inputs from the player on what kind of rocket to use (homing or simple)
- Creating a
think
function to change the behavior of the missile.
Taking inputs from the player
First we must have a place where we can store the input of the player. Therefore, we insert a variable homingMissle
in clientPersistant_t
typed structure. This structure is declared in the file g_local.h under the game project. The changed structure is shown below.
typedef struct {
clientConnected_t connected;
usercmd_t cmd; qboolean localClient; qboolean initialSpawn; qboolean predictItemPickup; qboolean pmoveFixed;
char netname[MAX_NETNAME];
int maxHealth; int enterTime; playerTeamState_t teamState; int voteCount; int teamVoteCount; qboolean teamInfo; int homingMissle;
} clientPersistant_t;
You must be wondering why I am using int
when I can use a boolean
. That's because in the code I am also introducing different types of homing missiles. Their think
functions are different. I will be explaining only the basic homing missile code here.
Once we have placed a variable here, we must initialize it with a default value whenever a new player is created. This is done in function ClientBegin(...)
in file g_client.c as shown below:
void ClientBegin( int clientNum )
{
gentity_t *ent;
...........
ent->client = client;
client->pers.homingMissle = 0;
client->pers.connected = CON_CONNECTED;
client->pers.enterTime = level.time;
...........
CalculateRanks();
}
Now, we have to take input from the player during the game and set it to the homingMissile
variable. If you have played Quake 3, you will know that commands can be given by the sequence "Press '~'. Then give command as "/CommandName command-parameters(if any)
". So let's say our command name is homing
.
We will not be using any parameters with the command. So this variable can be toggled within its value-range. The range used in this code is from 0
to 5
. i.e. When the player initiates the game, the value is 0
. Then when the player gives the homing
command, the value is 1
. Thereafter, 2
,3
,4
,5
and finally after 5
it will be back to 0
.
To achieve such functionality, we will have to introduce our command in the file g_cmds.c. This we do by modifying the method ClientCommand(...)
in this file.
void ClientCommand( int clientNum )
{
gentity_t *ent;
............
else if (Q_stricmp (cmd, "stats") == 0)
Cmd_Stats_f( ent );
else if (Q_stricmp (cmd, "homing") == 0)
Cmd_SetHoming_f (ent);
else
trap_SendServerCommand
( clientNum, va("print \"unknown cmd %s\n\"", cmd ) );
}
As you can see we also used a new method Cmd_SetHoming_f(...)
. This is declared as shown below:
void Cmd_SetHoming_f (gentity_t *ent)
{
if (ent->client->pers.homingMissle == 0)
{
trap_SendServerCommand( ent-g_entities, va
("print \"Homing Missiles with constant speed are on.\n\""));
ent->client->pers.homingMissle = 1;
}
else if (ent->client->pers.homingMissle == 1)
{
trap_SendServerCommand( ent-g_entities, va
("print \"Homing Missiles with variable speed are on.\n\""));
ent->client->pers.homingMissle = 2;
}
else if (ent->client->pers.homingMissle == 2)
{
trap_SendServerCommand( ent-g_entities, va
("print \"Homing Missiles with variable speed
and fireworks are on.\n\""));
ent->client->pers.homingMissle = 3;
}
else if (ent->client->pers.homingMissle == 3)
{
trap_SendServerCommand( ent-g_entities, va
("print \"Homing Missiles with variable speed,
fireworks and wide angle are on.\n\""));
ent->client->pers.homingMissle = 4;
}
else if (ent->client->pers.homingMissle == 4)
{
trap_SendServerCommand( ent-g_entities, va
("print \"Homing Missiles with variable speed,
fireworks and all view are on.\n\""));
ent->client->pers.homingMissle = 5;
}
else
{
trap_SendServerCommand( ent-g_entities, va
("print \"Homing Missiles are off.\n\""));
ent->client->pers.homingMissle = 0;
}
}
So now whenever the player gives command "/homing", the values will be toggled within the range and appropriate behavior will be exhibited by the missiles. You can also bind some key say 'h' to the homing command. Give the command /bind h homing.
As you can see, different values have different behaviors stated. The behavior for value=1
will be explained here, the rest will be briefly stated later.
Changing the behavior of the new missile
Now once we are done with accepting input, we need to change the behavior of the missile based on this value. As discussed before, to achieve this, we will have to create a new think
function that will have the necessary code to exhibit different behavior.
Re-stating: if you see the fire_rocket
method in g_missile.c under the game project you will find lines stating:
bolt->nextthink = level.time + 15000;
bolt->think = G_ExplodeMissile;
G_ExplodeMissile
is a think
function for the rocket. This means that after 15 seconds the rocket will explode. 15 seconds is the next think time here.
We will point it to a different think
function missile_think
. Also a homing missile needs to move a bit slower initially. This is shown below:
gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir) {
gentity_t *bolt;
............
bolt->classname = "rocket";
if(self->client->pers.homingMissle == 1)
{
bolt->think = missile_think;
bolt->nextthink = level.time + 1;
}
else if(self->client->pers.homingMissle == 2)
{
bolt->think = missile_think_variable_speed;
bolt->nextthink = level.time + 1;
}
else if(self->client->pers.homingMissle == 3
|| self->client->pers.homingMissle == 4
|| self->client->pers.homingMissle == 5)
{
bolt->think = missile_think_variable_speed_fireworks;
bolt->nextthink = level.time + 1;
}
else
{
bolt->nextthink = level.time + 15000;
bolt->think = G_ExplodeMissile;
}
............
............
bolt->s.pos.trType = TR_LINEAR;
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
VectorCopy( start, bolt->s.pos.trBase );
if (self->client->pers.homingMissle == 1)
{
VectorScale( dir, 500, bolt->s.pos.trDelta );
}
else
{
VectorScale( dir, 900, bolt->s.pos.trDelta );
}
SnapVector( bolt->s.pos.trDelta ); VectorCopy (start, bolt->r.currentOrigin);
return bolt;
}
The speed is initially set to 500. In my code, the speed increases once the target is found, but I feel it is completely a personal choice.
Now that we have pointed to another think
function, we must define it. Before defining the function, let me explain the behavior that the think
function will exhibit in the game environment.
- Defining: The homing missile will select a nearest target, steer towards it and blast into it.
- When the missile is fired and travelling straight, it should scan all the entities in its 'visibility distance' and 'visual cone'. The visual cone limits the visibility of the missile and allows it to become more realistic, else it can just take a 180 degree turn and hit some target behind.
- It should only select other player entities and should not select the firing-player and his team members (in case of a team match).
- The target should be directly visible. Since this is a virtual world, the visual cone mathematics does not solve this problem. We need to address it a bit differently.
The code for this think
function is also placed in g_missile.c just under the line "#define MISSILE_PRESTEP_TIME 50
". It is shown below:
#define MISSILE_PRESTEP_TIME 50
gentity_t *findNearestTargetInRadius
(gentity_t *ent, float rad, vec3_t *pTargetDirectionVector,
vec3_t missileNormalizedForwardDirection, double fVisionCone)
{
gentity_t *pEntity = NULL;
gentity_t *pTarget = NULL;
vec3_t pEntityMidBodyVector;
vec_t fEntityMidBodyVectorLengh=0;
vec_t fTargetDistance=0;
vec3_t temp_vector;
trace_t trace;
for (pEntity = g_entities; pEntity < &g_entities[level.num_entities];
pEntity++)
{
if (!pEntity->inuse) continue;
VectorAdd(pEntity->r.mins,pEntity->r.maxs,pEntityMidBodyVector);
VectorScale(pEntityMidBodyVector,0.5,pEntityMidBodyVector);
VectorAdd(pEntity->r.currentOrigin,pEntityMidBodyVector,
pEntityMidBodyVector);
VectorSubtract(pEntityMidBodyVector,ent->r.currentOrigin,
pEntityMidBodyVector);
fEntityMidBodyVectorLengh = VectorLength(pEntityMidBodyVector);
if (fEntityMidBodyVectorLengh > rad)
continue;
if(pEntity!=NULL)
{
if (!pEntity->client)
continue;
if (pEntity == ent->parent)
continue;
if (pEntity->health <= 0)
continue;
if (pEntity->client->sess.sessionTeam == TEAM_SPECTATOR)
continue;
if ( OnSameTeam( pEntity, ent->parent ) )
continue;
VectorCopy(pEntityMidBodyVector,temp_vector);
VectorNormalize(temp_vector);
if ( fVisionCone > 0.01
&&
DotProduct(missileNormalizedForwardDirection,
temp_vector) < fVisionCone )
continue;
trap_Trace (&trace, ent->s.pos.trBase, NULL, NULL,
pEntity->s.pos.trBase,
ent->s.number, MASK_SHOT );
if ( trace.contents & CONTENTS_SOLID )
continue;
if ((pTarget == NULL) ||
(fEntityMidBodyVectorLengh < fTargetDistance+100))
{
pTarget=pEntity;
VectorCopy(pEntityMidBodyVector,
(*pTargetDirectionVector));
fTargetDistance = fEntityMidBodyVectorLengh;
}
}
}
return pTarget;
}
void missile_think( gentity_t *ent ) {
gentity_t *pTarget = NULL;
vec3_t targetdir,forward;
int speed=900;
VectorCopy(ent->s.pos.trDelta, forward);
VectorNormalize(forward);
pTarget = findNearestTargetInRadius(ent,5000,&targetdir, forward,0.7);
if(pTarget!=NULL)
{
VectorMA(forward, 0.05, targetdir, targetdir);
VectorNormalize(targetdir);
VectorScale(targetdir, speed, ent->s.pos.trDelta);
}
VectorCopy( ent->r.currentOrigin ,ent->s.pos.trBase );
ent->s.pos.trTime = level.time;
ent->nextthink = level.time + 1;
}
I feel the comments placed above are self-explanatory, so I am not discussing the same. In the attached code, you will find multiple missile_think
functions and in total five different homing modes. Here's a brief on those:
Homing Missiles with constant speed
These are the ones shown above. Once launched the missile steers towards the target at the same speed.
Homing Missiles with variable speed
I like these one the most and they are designed for higher precision to hit the target. If the target is far enough, the missile will increase its speed to come close to it. Once it is quite close, it takes a relative measurement of the angle between itself and target. If the angle is too less, the missile will again increase its speed and hit the target before it moves out of sight. It is capable of shooting a target in mid-air almost every time.
Homing Missiles with variable speed and fireworks
Well, this idea came from the epic and one time everybody's favorite tele-serial Ramayan, where, in the wars one arrow fired would spawn multiple arrows. Similarly this function facilitates spawning of non-homing rockets when the target is closer. Absolutely no way to escape. Not much interesting to play with. But you'll get to see some great fireworks.
Homing Missiles with variable speed, fireworks and wide angle
The visual cone is more spread out. Rest is the same as above.
Homing Missiles with variable speed, fireworks and all view
Same as above. The visual cone is completely open. Expect the missile to take hair-pin turns when using this.
Concluding...
Here, in the code, I have set some values in the code, for the homing missile, such that it works with high precision and the target cannot escape. Now that's a kind of military scenario. In game playing, that will be a real tough scenario and will not be interesting. I leave it up to you to change the code so that the fun part in gaming is not lost.
Learning Quake 3 can be real fun. Some day it can be used to teach you game development, simulation, 3D geometry and vector mathematics. And besides playing games, its engine can be quite useful for creating 3D UI applications, researching on building architectures, etc.
At many places I have wrongly spelled "Missile" as "Missle". Please excuse that. May be I was hungry for "i" when writing this.
Please do not hesitate to ask queries/suggestions/issues/anything.
The updated demo images and article can be found here

Image after modification: Grunt being chased by the homing missile