< 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.


Part 3: - Missile behavior …. Continue reading >