This document introduces the QuakeC language and highlights the differences between usual QuakeC programming and QuakeMap-oriented programming.
Index
Introduction
The QuakeC language is a simplified C language for use with Quake. A large part of the behaviours found in Quake - doors, monsters, and much more - has been made in this QuakeC language, and this means it can be modified. id Software released the QuakeC source code (last version : 1.06), as well as the compiler they used. When compiled, QuakeC is a pseudo-code that can be interpreted by Quake. The compiled code is always put in a file named Progs.dat.
The traditional way of making changes in this code is to change parts of the source files and recompile the whole thing. This approach is neither practical - need to redistribute whole, modified files - nor modular - cannot easily mix several, unrelated code changes, as soon as two of them require changes in the same source file.
QuakeMap presents a solution to this problem : its integrated patch compiler. It is a full-featured QuakeC compiler, with the difference that it can't create Progs.dat files by itself. It works by patching - i.e. modifying - an existing Progs.dat file. This approach has the following advantages : you can apply several patches consecutively to any given Progs.dat file; and all the source files for the original Progs.dat are not required. This means you can directly take any Progs.dat and add any number of QuakeMap Add-ons to it. As source Progs.dat, you can take either id Software's original one, which is QuakeMap's default behaviour, or any other one found on the Internet - even if source code is not available !
Basics
First, you should download id Software's QuakeC source code. In theory, you don't need it, but when you are trying to enhance or change some behaviour, the first thing to do is to figure out where you must do these changes.
The difference between standard QuakeC programming and QuakeMap programming is that, in the first case, you can simply change parts of id Software's code, and in the second one you must write a new function to patch the old one, the one that holds the code you want to change. In QuakeMap, you can write a function exactly as you normally would, replacing any existing function just by writing a new version of it. However, the object of modulary is not to rewrite the entire function. What you will often be doing is writing patch functions. In this case, you give your function the same name as the patched function, but, to reuse the original's function behaviour, you don't need to rewrite it - you call it with the command : inherited();
The following example should make it clear. When a player is killed, the function ClientObituary
is called, which displays the appropriate message based on who killed the player (e.g. ...was destroyed by an ogre). To change the message, or add new conditions and corresponding new messages (e.g. when creating a new monster type), you write a new ClientObituary
function, testing the new conditions and reacting to them if they occur, and if they don't, you just call the inherited function, i.e. the old ClientObituary
, which will handle all the other cases itself. This approach lets any number of patches enhance the same ClientObituary
function : each one checks for particular conditions, and passes the message to the next one if it doesn't know how to handle this message itself. Practical example :
void(entity targ, attacker) ClientObituary = { if (targ.classname == "player" && attacker.classname == "monster_army") { // handle this case bprint (targ.netname); // display the player's name bprint (" has stupidly been shot by a dummy grunt\n"); } else // all other cases inherted (); // pass the message to the old ClientObituary };
Too bad it is not always so easy to figure out how to patch a function to change or enhance its behaviour. Look for another example in file ROCKETS.QME that comes with QuakeMap.
Enhancements in the QuakeC syntax
QuakeMap gives you a way to bind keys with actions. As usual, this is done with Impulses. The difference is that Impulse numbers, which are limited to the range 0 to 255, are assigned dynamically by QuakeMap. You can write, in your code :
bind "f", "Fire my special grenade", FireSpecialGrenade;
This allocates an unused Impulse number and assigns it to the call of your function FireSpecialGrenade. It also directs QuakeMap to prompt the user to choose the key he wants, when playing with this patch. "f"
is the default key, which can be changed. Then, QuakeMap writes a Quake configuration file that binds the choosen key to the Impulse number. The result is that whenever the player press on the key he choosed, your function is called.
There is a variation : if the assigned function (here FireSpecialGrenade
) has been defined as void(float KeyDown)
, it will be called both when the key is pressed and when it is released. The parameter KeyDown
can be used to determine whether the key was pressed or released.
There is another special QuakeC command : autoexec
. Any text you specify after this command is added to the configuration file that gets executed at Quake start-up. For example :
autoexec "alias abc def"; autoexec "alias abc \"+attack;wait;-attack\""; autoexec "alias abc \"", FireSpecialGrenade, ";wait;echo Fired grenade\"";
Note that \"
means a quote, like \n
means an end of line. Also note that you can put function names after autoexec
: the function name is translated into the corresponding impulse xxx
command, meaning that the function will be called at this point.
Abstract from EXPLORER.TXT
- The integrated QC compiler is a patch compiler - that is, it takes the original Progs.dat file, modifies it, and writes a new one. You don't have to recompile all the .qc files released by id Software. Anyway, you couldn't, because this compiler does not support some operations that are essential to produce a Progs.dat but that make no sense for patches, like modifying the system variables. - Unlike what you do usually with the .qc files, you should not distribute a whole, modified .qc file as a patch. You should only write a modified version of one or a few functions - only the ones that needs changes, and not a whole .qc file ! - To let the user play with multiple patches from multiple sources, you should, whenever possible, modify functions by only adding something at its begin or at its end. This is done trough the special "inherited" variable, which contains the address of the original function when you are overwriting one. See Rockets.qme for examples of this.
Using another base Progs.dat
As explained above, you can use with QuakeMap any compiled Progs.dat file, and add other code patches to it. You can't, however, mix several already compiled Progs.dat, so keep in mind that you have better use the source code, if available, as often as possible.
To use an existing Progs.dat, choose New, Import Wizard ; click on the button near the text Import an already compiled Progs.dat file, and select the file. Then, because QuakeMap will automatically assign
Impulse
numbers to the other code patches (if any), you must tell him which Impulse
codes are already used in this Progs.dat. In case of doubt, try entering some unusual number like 160
. At this stage, the Progs.dat is imported in QuakeMap. You don't need the original file any more.
If you want to add other QuakeMap Add-ons and mix them with the code you just imported, create file links to them (New, Make File links) and press the big GO! button to test your work. If, on the contrary, you are creating a QuakeMap Add-on like a new monster, you must import the other files related to this code, e.g. Models or sounds, and save the whole as a stand-alone Add-on. This operation is described in the document "New Monsters".
You can have troubles adding other code patches to some Progs.dat found on the Internet. If they made too many changes, QuakeMap won't be able to compile code patches relying on variables or functions no longer present. For example, TeamFortress releases Progs.dat with no constants in them, so any other code patch using standard constants - that is, almost any code patch - won't compile. Indeed, the constants are usually stored in the Progs.dat itself, so QuakeMap can usually rely on it to extract that kind of information. In the above case, the constants become Unkown identifiers. The solution is to rewrite the constants in a code patch and put it before the other ones. You can download ConstsQm.zip with this code patch and make a file link to it. Don't forget to put it first.
Incompatibilities
I hope my QuakeC compiler is mostly compatible with QCC, the standard one. However, here are the few changes you should be aware of :
The only information not present in Progs.dat is the result type of functions. QuakeMap always assume it is float
, if not void
. The only problem is when using vector
function results in expressions. You should not write v=normalize(v)*5;
because QuakeMap will interpret the *
as a float multiply. This example will raise a compile error because QuakeMap believes you are trying to assign a float to the vector v
. Write instead v=normalize(v); v=v*5;
You can't define System variables. System variables are the interface between Quake and the QuakeC code. There is no point to create new ones, because they are hard-coded in Quake and so can't be changed. You must use the ones already definied in the base Progs.dat file.
Model information is not recognized by QuakeMap, except its most useful part : the Frame definitions, e.g. $frame stand1 stand2 stand3...
The Model file is not used ; instead, the first defined frame is supposed to be the first one in the Model file, the second defined is supposed to be the second in file, etc. This seems to be always the case in id Software's original code, anyway. The frame counter is reset to 0 between various QuakeC entries, so put one Model AI per QuakeC entry.
There is no code size or variable number limit. A good point over QCC.
More about it
The "How-To" Manuals describe several examples of using QuakeC.
Read this introduction about the QuakeMap Explorer multi-entries model.
What is a QuakeMap add-on ?
Considerations about multi-player games : which files need to be copied where ?
Back to the main page
Date: 27.01.97, 2.02.97, by Armin Rigo