Q3 Coding
Creating a Basic Mod

Date : 17/01/99
Author(s) : Wilka
Skill(s) : Medium
Source Project(s) : Game
Revision : 2

This is a server side mod, so all changes will be for the 'game' code. I'll use text boxes for the code, so it's easy for you to copy & paste them in to the q3 source. This mod is based on one I wrote a while ago for SiN. It's not very complex, but it does cover all the basic's that most mods are made of from. In this mod (Weapons of Fury, or WoF for short) the aim is to get a kill with each weapon. Without dieing. The server sets what weapons, in what order, are used via a g_wofOrder cvar. By filling it with numbers that corresponded to a weapon. So if the server admin wants you do get 2 rocket launcher kills 1 rail kill, and a BFG kill to win, he would set g_wofOrder to "5579". Each time you die, you have to start again at the first weapon (a rocket launcher in this case). Now that you know what the mod is, here's how to make it.

Creating a New Cvar

The first thing we need to do is create a new console variable (cvar) so the server can set the weapon order. To do that, open up the file g_main.c and go to line 57. You'll see a list of vmCvar_t definitions like this:

We'll add our new cvar to the end of these, so under the last "vmCvar_t" add "vmCvar_t g_wofOrder". I'll use //Wilka and //W to mark my changes to original code, so it should now look like this :

Now move down to line 121. This is where q3 sets the default values and settings for the cvars. Go to the line starts "{ &g_allowVote", and add a command to the end of the line. So that we can put another item in the array. Every item, apart from the last one needs a comma at the end (after the close brace). Then add our new cvar, so it should now look like this :

The line you added tells q3 how you want your new cvar to be set up. Here's what each thing is for:
  • &g_wofOrder : This is the vmCvar_t that you want to to set. 
  • "g_wofOrder" : This is name of the cvar that you use in the console. It doesn't have to be the same name as your vmCvar_t variable. But it makes more sense if they are.
  • "2345678" : This is the default value of the cvar - it needs be enclosed in quotes, even if it's a number.
  • CVAR_SERVERINFO | CVAR_ARCHIVE| CVAR_LATCH : These are all one item (which is why there is no "," between them). They each set a prosperity of the cvar. CVAR_SERVERINFO tells q3 that cvar is a server info cvar, this will make it show up in the rules section GameSpy, or any time the server gets queried. You only use this for cvars that affect the game (i.e. fraglimit, sv_minping, etc). CVAR_ARCHIVE tells Quake3 that you want this cvar to be saved to q3config.cfg when Quake3 is closed. This makes any changes to to the cvar permanent, because they are loaded back in when q3 starts. Lastly CVAR_LATCH, means that any changes to this cvar do not take effect until the game is restarted (i.e. a map change, or a map_restart). If you want to use more than one option for your cvar, you need to 'or' them together with | (like I have here)
  • 0 : This is the modification count for the cvar, you should always start this at 0. Otherwise you wont know how many times it's been changed. Although I'm not sure why you'd care how many times it was changed :)
  • qfalse : This can be qfalse or qtrue. It tells q3 if you want clients to be notified when this cvar is changes via a printed message.

Now that we've got our new cvar added, we can move on to actually using it.

Modifying The Client Struct

We need some way to keep track of what the clients current weapon position. The easiest way to that is to simply add an 'int' to the player's struct. Go to line 284 in g_local.h, then under "int timeResidual;" add our new variable so it looks like this.

This new variables will used to work out what weapon the player will get when he makes his next kill. Now we add a function that will move this client to the next weapon, and update the iCurrentWeapon variable.

Adding New Functions

We'll call this function Wof_NextWeapon, and since it'll be used for the client we'll put it in the g_client.c file. Open up g_client.c and scroll to the end, then add the following code.

That handles giving them the next weapon. The way this works is the first time a client spawns, their current weapon gets set to -1. Then when Wof_NextWeapon is called, their current weapon variables gets increased by 1 (so it's now 0), and iWep stores the number at position 0 in the g_wofOrder string. So that's the first character. We then check what weapon they need in the switch block. We then set the STAT_WEAPONS item in ps.stats array to whatever weapon they should have (this gets rid of any weapons they had before). Give them some ammo for the weapon, and set their current weapon (so it forces them to switch from the old weapon) with the line "client->ps.weapon = WP_<weapon>".

When we exit the switch, we force the weapon state to ready and play a sound so the client releases they have change weapons (the change is instant, so they could miss it).

The only problem with this is that they're is no way for the player to win. So we need to add a default case to our switch block. The default case will be used when none of the other case's apply. All string's need to end in a 0, and since we are only checking for char's that are numbers (i.e. a char '0' is not the same as an int 0), when we reach the end of the string (or anything that's not a number we are looking for), the default case is used. So go back to your Wof_NextWeapon function, and add this to the end of the switch (after the BFG case).

Now we need let this function know that LogExit and out new cvar exist otherwise we'll get an error when we try and compile the mod. So scroll up to the top of the file, and change it so it looks like this.

the extern keyword tells the compiler that this variable is declared in another (external) file. If we didn't use extern we would get an error because we'd be trying to declare the same thing twice. All that's left for us to do now is tie it all together so our mod works the way we want it to.

Joining The Pieces

We need to make sure that the client's current weapon stars at -1 when they spawn. Otherwise when they die they wont start back at the first weapon. Then we need to call Wof_NextWeapon so the move on to the first weapon. Do that  to line 996 in g_client.c (the ClientSpawn function), and just after G_UseTargets is called add this code.

We need to put it after G_UseTargets so that any target_give entities on the map don't give the client weapons they shouldn't have. Now need to make the client move to the next weapon when they get a kill. So open up the g_combat.c file and go to line 240 (the player_die function). Then on the line before AddScore we need to move the attacker to his next weapon. So change the code to look like this.

Now we need to make sure that player_die and ClientBegin know about our Wof_NextWeapon function. So open g_local.h and go to the end of the file. Then add the following code.

You could compile and play your mod now, and it would work. But there is a problem with it. Players can pick up another weapon and start using that. So we need to stop them being able to pickup weapons. To do that we need to go the Pickup_Weapon function in g_items.c. Go down to line 163 and comment out the part that gives the client the weapon (we still want them to have the ammo, so leave that alone). It should look like this when your done.

Now when they collect a weapon, they only get the ammo for it. The mod is almost complete, we just need to add a way for the player to go back a weapon in case they run out of ammo (they're might not be any ammo for the weapon on the map they're playing).

Now when a player gets a kill, they will move on to the next weapon. But if that weapon isn't already on the map, the clients wont have it memory so they will need to load it. Which will cause a slight 'hitch' in the game (the same thing happens when you bring up the scores and you don't have the model loaded for a player that has joined). While it's not a good idea to load all of the players on game start because they take up a lot of ram, weapons are a lot smaller. So we want to precache them  so when a player loads the map, they load all the weapons as well. To do that we'll need to edit the ClearRegisteredItems function on line 576 of g_items.c. Just after the default weapons are registed, we want to add all the other weapons. So change the function to look like this.

It would have been better if we only loaded the weapons that we needed (we can find out by looking at g_wofOrder), but you should be able to work out how to that for yourself.

Adding A New Command

It's pretty easy to add a new command. What you need to do is go to ClientCommand in g_cmds.c, then on line 1094 add your new command like this.

Now when the client use the "wof_backWeapon" command, they will be moved back 1 weapon. We need to subtract 2 from iCurrentWeapon, so that their current weapon gets set to the one before the previous weapon. Then when Wof_NextWeapon adds one to iCurrentWeapon, they will be set to the previous weapon.

Printing Messages

It would be nice to let the client know this server is a non-standard dm server when the join, so they don't get confused about not being able to pickup weapons. While we're at it we should let them know what g_wofOrder is when they join. To do that we'll need to edit the ClientBegin function. Go to line 814 in g_client.c, we need to add out print messages after "<Player> has joined the game." gets printed. So change it to look like this.

The very last thing we need to do is change the gameversion to "wof". So go to line 12 in g_local.h and change it to this.

Now your mod is complete. If you feel like giving it a bit more 'polish', you could let the player know how many more kills they need to win. A good place for that happen would be the Wof_NextWeapon function, and you might find the strlen function (in bg_lib.c) handy. I'll leave the rest up to you.

Almost all mods are made of these basic parts, so once you understand all of this you'll be able to make whatever mod you like, well almost, your still limited to what Quake3 will let you change :)

Thanks to Rick Terrill for letting me know what the "0" was for in the cvar struct. I also fixed a typo where I used "WofNextWeapon" when I meant to type "Wof_NextWeapon", so sorry to any of you that had trouble compiling the code because of it (thanks Jim Frantzen for letting me know).


Tutorial by Wilka
Wiretap Development
Wiretap HQ