Modifying Quake 3 - part 2- Gamer inputs
< Continued from Part 1: Compiling Quake 3 source code
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 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; //Game time here is in milliseconds.
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, 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. 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 missile.
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.
// client data that stays across multiple respawns, but is cleared
// on each level change or team change at ClientBegin()
typedef struct {
clientConnected_t connected;
usercmd_t cmd; // we would lose angles if not persistant
qboolean localClient; // true if "ip" info key is "localhost"
qboolean initialSpawn; // the first spawn should be at a cool location
qboolean predictItemPickup; // based on cg_predictItems userinfo
qboolean pmoveFixed; //
char netname[MAX_NETNAME];
int maxHealth; // for handicapping
int enterTime; // level.time the client entered the game
playerTeamState_t teamState; // status in teamplay games
int voteCount; // to prevent people from constantly calling votes
int teamVoteCount; // to prevent people from constantly calling votes
qboolean teamInfo; // send team overlay updates?
<strong>int homingMissle; // is homing missle on for the client</strong>
} clientPersistant_t;
You must be wondering why I am using int
when I can use a boolean
. That’s because, in the future, I want to introduce different types of homing missiles. Their think functions are different. Over here, I will be explaining only the basic homing missile code.
Once we have placed a varible 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;
<strong>client->pers.homingMissle = 0;</strong>
client->pers.connected = CON_CONNECTED;
client->pers.enterTime = level.time;
...........
// count current clients and rank for scoreboard
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 lets 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 intiates game the value is 0
. Then when player gives 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
//New method added
void Cmd_SetHoming_f (gentity_t *ent)
{
//0 -> off
//1 -> Constant speed
//2 -> Variable speed
//3 -> Fireworks with varible speed
//4 -> Fireworks with varible speed and wide angle
//5 -> Fireworks with varible speed and all view
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 explained later.