This is the sidebar
QuakeC is a very simplified dialect of the well-known C programming language, as used by Quake. Nexuiz uses the FTEQCC dialect of QuakeC, so only this one will be described (as well as some common extensions among Quake engines).
To see what QuakeC looks like, here is an example:
// needed declarations: float vlen(vector v) = #12; entity nextent(entity e) = #47; .string classname; .vector origin; // ... entity findchain(.string fld, string match) { entity first, prev; entity e; first = prev = world; for(e = world; (e = nextent(e)); ++e) if(e.fld == match) { e.chain = world; if(prev) prev.chain = e; else first = e; prev = e; } return first; } // ... entity findnearestspawn(vector v) { entity nearest; entity e; for(e = findchain(classname, "info_player_deathmatch"); e; e = e.chain) if(!nearest) nearest = e; else if(vlen(e.origin - v) < vlen(nearest.origin - v)) nearest = e; return nearest; }
(Note: findchain is implemented in QuakeC for demonstration purposes only so one can see how to build a linked list, as this function is already built in to the engine and can be used directly)
Here is a forum on Inside3D where you can read more about QuakeC and ask questions.
For available functions in QuakeC, look in the following places:
To declare a variable, the syntax is the same as in C:
float i;
However, variables cannot be initialized in their declaration for historical reasons, and trying to do so would define a constant.
Whenever a variable declaration could be interpreted as something else by the compiler, the var keyword helps disambiguating. For example,
float(float a, float b) myfunc;
is an old-style function declaration, while
var float(float a, float b) myfunc;
declares a variable of function type. An alternate and often more readable way to disambiguate variable declarations is using a typedef:
typedef float(float, float) myfunc_t; myfunc_t myfunc;
A variable declared in the global scope has global scope, and is visible starting from its declaration to the end of the code. The order the code is read in by the compiler is defined in the file progs.src.
A variable declared inside a function has function scope, and is visible starting from its declaration to the end of the function (not to the end of the block).
Some variables are declared in sys.qh. Their declarations or names should never be changed, as they have to match the order and names of the variables in the file file progdefs.h of the engine exactly, or the code won't load. The special markers end_sys_globals and end_sys_fields are placed to denote the end of this shared declaration section.
Quake only knows four elementary data types: the basic types float, vector, string, and the object type entity. Also, there is a very special type of types, fields, and of course functions. FTEQCC also adds arrays, although these are slow and a bit buggy. Note that there are no pointers!
This is the basic numeric type in QuakeC. It represents the standard 32bit floating point type as known from C. It has 23 bits of mantissa, 8 bits of exponent, and one sign bit. The numeric range goes from about 1.175e-38 to about 3.403e+38, and the number of significant decimal digits is about six.
As float has 23 bits of mantissa, it can also be used to safely represent integers in the range from -16777216 to 16777216. 16777217 is the first integer float can not represent.
Common functions for float are especially ceil, floor (working just like in C, rounding up/down to the next integer), and random, which yields a random number r with 0 <= r < 1.
This type is basically three floats together. By declaring a vector v, you also create three floats v_x, v_y and v_z (note the underscore) that contain the components of the vector.
Vectors can be used with the usual mathematical operators in the usual way used in mathematics. For example, vector + vector simply returns the sum of the vectors, and vector * float scales the vector by the given factor. Note however that dividing a vector by a float is NOT supported, one has to use vector * (1 / float) instead. Multiplying two vectors yields their dot product of type float.
Common functions to be used on vectors are vlen (vector length), normalize (vector divided by its length, i.e. a unit vector).
Vector literals are written like '1 0 0'.
COMPILER BUG: always use vector = vector * float instead of vector *= float, as the latter creates incorrect code.
A string in QuakeC is an immutable reference to a null-terminated character string stored in the engine. It is not possible to change a character in a string, but there are various functions to create new strings:
ftos and vtos convert floats and vectors to strings. Their inverses are, of course, stof and stov, which parse a string into a float or a vector.
strcat concatenates 2 to 8 strings together, as in strcat(“a”, “b”, “c”) == “abc”
strstrofs(haystack, needle, offset) searches for an occurence of one string in another, as in strstrofs(“haystack”, “ac”, 0) == 5. The offset defines from which starting position to search, and the return value is -1 if no match is found. The offset returned is 0-based, and to search in the whole string, a start offset of 0 would be used.
substring(string, startpos, length) returns part of a string. The offset is 0-based here, too.
Note that there are different kinds of strings, regarding memory management:
The main object type in QuakeC is entity, a reference to an engine internal object. An entity can be imagined as a huge struct, containing many fields. This is the only object type in the language. However, fields can be added to the entity type by the following syntax:
.float myfield;
and then all objects e get a field that can be accessed like in e.myfield.
The special entity world also doubles as the null reference. It can not be written to other than in the spawnfunc_worldspawn function that is run when the map is loaded, and is the only entity value that counts as false in an if expression. Thus, functions that return entities tend to return world to indicate failure (e.g. find returns world to indicate no more entity can be found).
If a field has not been set, it gets the usual zero value of the type when the object is created (i.e. 0 for float, string_null for string, '0 0 0' for vector, and world for entity).
A reference to such a field can be stored too, in a field variable. It is declared and used like
.float myfield; // ... // and in some function: var .float myfieldvar; myfieldvar = myfield; e.myfieldvar = 42;
Field variables can be used as function parameters too - in that case you leave the var keyword out, as it is not needed for disambiguation.
Functions work just like in C:
float sum3(float a, float b, float c) { return a + b + c; }
However, the syntax to declare function pointers is simplified:
typedef float(float, float, float) op3func_t; var float(float a, float b, float c) f; op3func_t g; f = sum3; g = f; print(ftos(g(1, 2, 3)), "\n"); // prints 6
Also note that the var keyword is used again to disambiguate from a global function declaration.
In original QuakeC by Id Software, this simplified function pointer syntax also was the only way to define functions (you may still encounter this in Nexuiz code in a few places):
float(float a, float b) sum2 = { return a + b; }
A special kind of functions are built-in functions (defined by the engine). These are imported using so-called built-in numbers, with a syntax like
string strcat(string a, string b, ...) = #115;
Just like in C, the void type is a special placeholder type to declare that a function returns nothing. However, unlike in C, it is possible to declare variables of this type, although the only purpose of this is to declare a variable name without allocating space for it. The only occasion where this is used is the special end_sys_globals and end_sys_fields marker variables.
As the QuakeC virtual machine provides no pointers or similar ways to handle arrays, array support is added by FTEQCC and very limited. Arrays can only be global, must have a fixed size (not dynamically allocated), and are a bit buggy and slow. Almost as great as in FORTRAN, except they can't be multidimensional either!
You declare arrays like in C:
#define MAX_ASSASSINS 16 entity assassins[MAX_ASSASSINS]; #define BTREE_MAX_CHILDREN 5 .entity btree_child[BTREE_MAX_CHILDREN]; #define MAX_FLOATFIELDS 3 var .float myfloatfields[MAX_FLOATFIELDS];
The former is a global array of entities and can be used the usual way:
assassins[self.assassin_index] = self;
The middle one is a global array of (allocated and constant) entity fields and not a field of array type (which does not exist), so its usage looks a bit strange:
for(i = 0; i < BTREE_MAX_CHILDREN; ++i) self.(btree_child[i]) = world;
Note that this works:
var .entity indexfield; indexfield = btree_child[i]; self.indexfield = world;
The latter one is a global array of (assignable) entity field variables, and looks very similar:
myfloatfields[2] = health; self.(myfloatfields[2]) = 0; // equivalent to self.health = 0;
Do not use arrays when you do not need to - using both arrays and function calls in the same expression can get messed up (COMPILER BUG), and arrays are slowly emulated using functions ArrayGet*myfloatfields and ArraySet*myfloatfields the compiler generates that internally do a binary search for the array index.
This section deals with language constructs in FTEQCC that are not similar to anything in other languages.
There is a second way to do a negated if:
if not(expression) ...
It compiles to slightly more efficient code than
if(!expression) ...
and has the notable difference that
if not("") ...
will not execute (as ”” counts as true in an if expression), but
if(!"") ...
will execute (as both ”” and string_null is false when boolean operators are used on it)..
Some patterns in code that are often encountered in Nexuiz are listed here, in no particular order.
The usual way to handle classes in Quake is using fields, function pointers and the special property classname.
But first, let's look at how the engine creates entities when the map is loaded.
Assume you have the following declarations in your code:
entity self; .string classname; .vector origin; .float height;
and the engine encounters the entity
{ "classname" "func_bobbing" "height" "128" "origin" "0 32 -64" }
then it will, during loading the map, behave as if the following QuakeC code was executed:
self = spawn(); self.classname = "func_bobbing"; self.height = 128; self.origin = '0 32 -64'; spawnfunc_func_bobbing();
We learn from this:
Methods are represented as fields of function type:
.void() think;
and are assigned to the function to be called in the spawn function, like:
void func_bobbing_think() { // lots of stuff }
void spawnfunc_func_bobbing() { // ... even more stuff ... self.think = func_bobbing_think; }
To call a method of the same object, you would use
self.think();
but to call a method of another object, you first have to set self to that other object, but you typically need to restore self to its previous value when done:
entity oldself; // ... oldself = self; self.think(); self = oldself;
A very common entry point to QuakeC functions are so-called think functions.
They use the following declarations:
.void() think; .float nextthink;
If nextthink is not zero, the object gets an attached timer: as soon as time reaches nextthink, the think method is called with self set to the object. Before that, nextthink is set to zero. So a typical use is a periodic timer, like this:
void func_awesome_think() { bprint("I am awesome!\n"); self.nextthink = time + 2; }
void spawnfunc_func_awesome() { // ... self.think = func_awesome_think; self.nextthink = time + 2; }
One common way to loop through entities is the find loop. It works by calling a built-in function like
entity find(entity start, .string field, string match) = #18;
repeatedly. This function is defined as follows:
It can be used to enumerate all entities of a given type, for example “info_player_deathmatch”:
entity e; for(e = world; (e = find(e, classname, "info_player_deathmatch")); ) print("Spawn point found at ", vtos(e.origin), "\n");
There are many other functions that can be used in find loops, for example findfloat, findflags, findentity.
Note that the function findradius is misnamed and is not used as part of a find loop, but instead sets up a linked list of the entities found.
An alternate way to loop through a set of entities is a linked list. I assume you are already familiar with the concept, so I'll skip information about how to manage them.
It is however noteworthy that some built-in functions create such linked lists using the entity field chain as list pointer. Some of these functions are the aforementioned findradius, and findchain, findchainfloat, findchainflags and findchainentity.
A loop like the following could be used with these:
entity e; for(e = findchain(classname, "info_player_deathmatch"); e; e = e.chain) print("Spawn point found at ", vtos(e.origin), "\n");
The main advantage of linked lists however is that you can keep them in memory by using other fields than chain for storing their pointers. That way you can avoid having to search all entities over and over again (which is what find does internally) when you commonly need to work with the same type of entities.
Error handling is virtually not existent in Quake C code. There is no way to throw and handle exceptions.
However, built-in functions like fopen return -1 on error.
To report an error condition, the following means are open to you:
In the map editor, entities can be connected by assigning a name to them in the target field of the targeting entity and the targetname field of the targeted entity.
To QuakeC, these are just strings - to actually use the connection, one would use a find loop:
entity oldself; oldself = self; for(self = world; (self = find(self, targetname, oldself.target)); ) self.use(); self = oldself;
As the find loop for target and targetname causes the engine to loop through all entities and compare their targetname field, it may make sense to do this only once when the map is loaded.
For this, a common pattern is using the pre-defined enemy field to store the target of an entity.
However, this can't be done during spawning of the entities yet, as the order in which entities are loaded is defined by the map editor and tends to be random. So instead, one should do that at a later time, for example when the entity is first used, in a think function, or - the preferred way in the Nexuiz code base - in an InitializeEntity function:
void teleport_findtarget() { // ... self.enemy = find(world, targetname, self.target); if(!self.enemy) // some error handling... // ... }
void spawnfunc_trigger_teleport() { // ... InitializeEntity(self, teleport_findtarget, INITPRIO_FINDTARGET); // ... }
InitializeEntity functions are guaranteed to be executed at the beginning of the next frame, before the think functions are run, and are run in an order according to their priorities (the INITPRIO_ constants).
With default compile options (i.e. if the option -flo is not passed to the compiler), boolean expressions are evaluated fully. This means that in
if(!flag && SomeComplexFunction(self)) ...
SomeCompexFunction is always evaluated, even if flag is true. To avoid this, one can use:
if(!flag) if(SomeComplexFunction(self)) ...
Do not count on the modifying and reading operators like += or ++ to always work. Using them in simple cases like
a += 42; for(i = 0; i < n; ++i) ...
is generally safe, but complex constructs like
self.enemy.frags += self.value--;
are doomed. Instead, split up such expressions into simpler steps:
self.enemy.frags = self.enemy.frags + self.value; self.value -= 1;
The compiler warning RETURN VALUE ALREADY IN USE is a clear indicator that an expression was too complex for it to deal with it correctly. If you encounter the warning, do make sure you change the code to no longer cause it, as the generated code will be incorrect then.
Also, do not use the += like operators on vectors, as they are known to create incorrect code and only operate on the x component of the vector.
Mixing function calls with array dereferencing, or doing more than one array dereferencing in the same expression, is known to create incorrect code. Avoid constructs like
print(ftos(floatarray[i]), " --> ", stringarray[i], anotherstringarray[i], "\n");
as the array dereferencings and the ftos return value are likely to overwrite each other. Instead, simplify it:
float f; string s, s2; // ... f = floatarray[i]; s = stringarray[i]; s2 = anotherstringarray[i]; print(ftos(f), " --> ", s, s2, "\n");
The pitch angle is inverted between these two functions. You have to negate the pitch (i.e. the x component of the vector representing the euler angles) to make it fit the other function.
As a rule of thumb, vectoangles returns angles as stored in the angles field (used to rotate entities for display), while makevectors expects angles as stored in the v_angle field (used to transmit the direction the player is aiming). There is about just as much good reason in this as there is for 1:1 patch cables, just deal with it.
The server-side code calls the following entry points of the QuakeC code:
GFDLcontent Unless mentioned on the licensing page, the work on this page is licensed under the GNU Free Documentation License. The author states that the text and images can be used within the restrictions of this license (for example, they can be incorporated into certain free encyclopedias such as Wikipedia). | |
Kindly hosted by NexuizNinjaz.com |