- break;
Jumps outside of the current for/while/until/do loop, or switch statement.
- continue;
Jumps back to the start of the current loop.
- return
return; //for functions defined as returning void
return foo; // for functions with an actual return type.
return = foo; // for __accumulate functions that wish to execute accumulated code instead of skipping it all.
__exit; //exit as if reaching the end of the function. This is fantastic for confusing decompilers but otherwise should not be used.
The accumulate version above sets an implicit variable which is automatically returned once control reaches the end of the function. The other forms will leave the function there and then.
For compatibility reasons, fteqcc does not strictly enforce return types, however this is bad practise and you really should fix any related warning if you want to avoid unintended type punning.
goto foo;
Jumps to another location of the current function. The location must be marked.
goto foo;
unreachablecode();
foo:
{ }
Blocks are a convienient way to group multiple statements.
Note that any control statement documented as taking either a block or a statement does so interchangably (and many programming guides recomend that blocks ALWAYS be used.
Note that blocks can be used outside of control statements too. This allows for weird grouping or preprocessor workarounds.
if (condition) statement;
The most simple conditional branch.
If the condition is considered true, then the code will be executed, otherwise it will be skipped. If the expression is followed by an else satement then that will be executed if the primary block was not.
Large 'else if' chains can be constructed, but you may find switch statements to be more readable.
At this point, we have to ask ourselves what the nature of truth really is. Not in a philosophical sense, but in a programmery sense.
QuakeC looks for truth in multiple ways, and all control statements use the same basic form of truth, which depends upon types, but unitialised variables are always FALSE (except field references, which are typically auto-initialised).
- int
False when 0, true when anything else.
- float
False when +0, true when anything else. Note that -0 is normally TRUE, but some engines might treat this as false regardless, so don't expect things to make sense. There's a compile-flag to treat if(-0) as FALSE, for a small performance hit.
- strings
False when NULL, true when not NULL. Note that empty strings are not the same as NULL.
- functions
Only the null function is false (assignments from __NULL__ or other null functions can be used to reset a function reference to null). Other functions (including SUB_Null) are considered true.
- vectors
By default, fteqcc compares all three components against +/-0. Other QC compilers will typically test the _x component only.
- fields
Tests for null... Note that (typically) the modelindex field aliases the null field, so would be considered false.
- entities
The world entity is equivelent to null, and thus false. All other entities are considered true.
- pointers
__NULL__ is false, otherwise true.
- __variant
Danger... don't do this at home.
- arrays, structs, unions
These complex types cannot be evaluated for truth. You will need to evaluate a specific index or member instead.
HexenC adds an inverted form of if statements:
if not(condition)
break;
Which is especially useful to test the return value of eg fgets, allowing you to detect end-of-file conditions without breaking when encountering empty lines - note that if(!condition) tests for empty instead of null, and is thus not suitable in all cases.
for (initial; condition; increment) statement;
Like in C. The initial terms are executed, then the loop will restart for as long as the condition evaluates to true. The increment term is executed each time the loop is restarted (including via continues) but not the initial time.
Multiple initial terms or increments can be achieved via use of the comma operator, while the condition term must use the && or || operators.
Declarations are permitted inside the initial term, but have scope only within the loop.
while (condition) statement;
do while
do
{
statements();
} while(condition);
__state
This is an alternative way to set .frame, .think, and .nextthink in a single opcode.
__state [framenumber, functiontothink]; //quake style, accepts expressions, however do not use pre-increment nor post-increment as this is potentially ambiguous - use brackets if you need this.
__state [++ firstframe .. lastframe]; //hexen2 style, MUST use immediates / frame macros.
For the hexen2-style version, the animation automatically repeats selecting(wrapping) the frame. The cycle_wrapped global will be set according to whether the animation wrapped or not (starting from a frame outside of the range does not count as a wrap).
switch / case / default
switch(expression)
{
case 0:
statements;
break;
case 1..5: //ranges are inclusive, so this includes 5 but not 5.001
statements;
break;
case 6: //falls through
case 7:
statements;
break;
default: //if none of the above cases matched (like 8 or .3)
statements;
break;
}
Switches allow a slightly cleaner alternative to massive if-else-if-else chains.
The case statements define various possible values, with the following code being executed. The 'default' statement defines a fallback location to execute from if none of the cases matched the expression's value. Note that execution will continue through any later cases, so be sure to use break statements to prevent undesired fall-throughts.
Cases may be either a single value, or in the case of a numeric expression they may be a range of values seperated by a double-dot (eg: case 0..1:foo;break;). Cases are not required to be constants, but they must not be expressions (which means fixed array indexes are acceptable, but not dynamic indexes.
__thinktime
__thinktime ent : delay;
is equivelent to ent.nextthink=time+delay;
When using -Th2 on the commandline, the double-underscores are not required.
__until
__until(condition) {statements;}
A flipped version of while loops - the loop will repeat until the condition becomes true.
When using -Th2 on the commandline, the double-underscores are not required.
__loop
__loop { statements; }
Generally its better to use while(1){} or for(;;){} instead
When using -Th2 on the commandline, the double-underscores are not required.
- arrays
FTEQCC supports single-dimension arrays.
float foo[] = {1,2,3,4};
will thus define a float array and will infer its length as 4. If you need to use an explicit length, or you do not wish to initialise the array, then you must put the needed length inside the square brackets.
Note that arrays are 0-based, so an array defined as float foo[2]; can be accessed as foo[0] or foo[1], but any other value (including foo[2]) is out of bounds. Indexes will be rounded down.
foo.length can be read if you wish to know how long an array is (especially if the length was inferred for some reason)
Dynamic indexes ARE supported in all cases, however they may come at a significant performance loss if you do not have extended opcodes enabled, so it is generally preferable to unroll small loops such that constant indexes can be used.
Dynamic lengths are not supported at this time.
Arrays are also supported within structs, allowing for (clumsy) multi-dimensional arrays.
- fields
Fields are defined by prefixing the field's type with a dot.
Every single entity will then have its own instance of the data, accessed via eg: self.myfield
Fields are assumed to be constants, with any uninitialsed fields reserving space in every single entity in order to hold the data. Fields defined as var or initialised to the value of another field will NOT consume any space within each entity.
.float foo; //const, allocates storage.
var .float bar = foo;
void() fnar =
{
self.foo = 42;
if (self.bar != 42)
dprintf("the impossible happened\n");
};
Note that field references are variables in their own right. They can be defined as arrays, they can be passed to functions etc (eg the find builtin depends upon it).
A word of warning though - field references will not appear in most saved game formats. That's probably fine for deathmatch, but if you're using them for singleplayer then be sure to initialise them (even if to the value of another).
functions
In QuakeC, functions are both a function reference/variable, and typically a function immediate/body, much like a string is both a string reference, and the string data itself.
So function definitions are initialised in basically the same way as any other variable, where the function-type syntax is basically just returntype(arglist), eg:
returntype(arglist) name = functionbody;
functionbody can then be either some other function, or a function immediate.
As a special exception for C compatibility, the arglist parenthasis can follow the function's name instead, and when the initialiser is an immediate, the equals and trailing semi-colon tokens are optional.
Note that function immediates are what contain the bulk of code, and can only be present within the context of a function type - meaning either in a function initialisation or via a cast (creating an annonymous function - note that these are not safe for saved games, so try to avoid their use in ssqc).
Because functions are really function references, you can define them as var, and by doing so you can remap/wrap them at any point you wish. function types can also be used freely within function arguments or anywhere else.
void() somename = {}; //QC-style function
void somename(void) {} //C-style
void() foo = [$newframe, nextfunc] {}; //QC state function (function contains self.frame = $newframe; self.think = nextfunc; self.nextthink = time+0.1;)
var void() dynreference; //uninitialised function reference, for dynamically switching functions.
var void() dynreference = somename; //initialised function references work too
void(vector foo, float bar) func = {}; //function with two arguments.
void(float foo = 4) func = {}; function that will be passed 4 for the first argument if it is omitted.
void(... foo) func = {for(float f = 0; f < foo; f++) bprint(va_arg(f, string));}; //variable-args function that passes each arg to another.
void(optional float foo, ...) func = #0; //name-linked builtin where the first arg may be ommitted but must otherwise be a float, with up to 7 other args.
void(void) func = #0:foo; //unnumbered-but-named builtin.
void(float foo) func : 0; //hexenc builtin syntax
Note that QC can only tell how many arguments were passed using the variable-argument argcount form. Any optional arguments that were omitted will have undefined values. Any argumentsspecified after an optional argument must be omitted if the prior arg is omitted but otherwise must be specified unless they are optional themselves, or preinitialised.
Preinitialised non-optional arguments can be omitted, their preinitialised value will be passed in this case. If the argument list is empty (ie: two adjacent commas in the call), then the argument's default value will be passed automatically regardless of whether the argument is considered optional.
Builtins can only accept up to 8 args. Non-builtins have a much higher limit. Vectors count as 1 argument (and thus allow you to pack additional information). Structs can be passed but will be counted as ceil(sizeof(thestruct)/sizeof(vector)) arguments.
Named builtins can be used by some (but not all) engines to avoid numbering conflicts. Typically these builtins must also be numbered as 0.
typedef
typedef type newname;
Typedefs allow the creation of an alias for types.
enum / enumflags
enum int
{
FIRST,
SECOND,
THIRD,
NINTH=8
};
Provides an easy way to define a set of constants.
enum: Each name will default to one higher than the previous name, with the first defaulting to 0.
enumflags: Each name will default to twice the previous name, with the first defaulting to 1, ideal for bit flags.
Alternatively, if you want to ensure that only known values are stored into an enum type, you can use the following:
enum typename : string
{
FIRST = "first",
SECOND = "second",
THIRD = "third"
};
typename foo;
foo = FIRST; //okay
foo = "fourth"; //warning
struct
Allows you to define a struct, which is useful for boxing multiple related variables.
typedef struct
{
vector rgb;
float a;
} rgba_t;
rgba_t opaque_red = {'1 0 0', 1};
void(rgba_t c) fillscreen =
{
drawfill([0,0], screensize, c.rgb, c.a, 0};
};
void() drawstuff =
{
fillscreen(opaque_red);
};
These are especially useful when combined with pointers, but can also simplify copies.
union
Equivelent to structs, except all struct members start at the same offset. This can be used for either type punning or compressing mutually-exclusive fields inside complex struct layouts.
By nesting structs, unions and arrays, you can get some quite complex data structures.
Note that unions and structs define within unions or structs do not need to be named. Members from child structs will automatically be accessed as if they were part of the containing stuct.
struct
{
float type;
union
{
struct
{
float f;
vector bar;
};
struct
{
string s[2];
};
};
} foo[8];
float() foobar =
{
if (foo[4].type)
return stof(foo[4].s[1]);
else
return foo[4].f;
};
class
class foo : entity
{
float interval;
virtual void() think =
{
centerprint(enemy, this.message);
nextthink = time+interval;
};
nonvirtual void(enemy e) setEnemy =
{
enemy = e;
};
void() foo =
{
nextthink = time+interval;
};
};
void() someiplayerfunction =
{
foo myfoo = spawn(foo, message:"Hello World", interval:5);
myfoo.setEnemy(self);
};
The above is a terrible example, sorry.
If the parent class is omitted, entity will be assumed.
Class constructors double up as spawn functions, and the spawn intrisic works in the same way - the named members will be set before the constructor is called, instead of passing arguments to the constructor (which avoids the need for overloads etc).
The 'interval' field above is defined as a class field. Such class fields are valid ONLY on entities of that class, which allows for more efficient memory usage.
Member functions can be defined as virtual (such functions are technically pre-initialised fields, and thus compatible with things like think or touch), non-virtual (read: non-inherited), or static (where 'this' cannot be used).
Public, private, protected are parsed like in C++, but ignored.
Only a single parent type can be inherited, and it must be a class or the general entity type.
pointer
(Note: not a keyword)
As in C, pointers are defined using an asterisk prefix on the type's name.
float *ptr; //define a pointer-to-float
ptr = &self.frame; //obtain the address of a variable (aka: (obtain a) reference)
*ptr = *ptr + 1; //access through the pointer (aka: dereference)
In order to define a field that contains a pointer (instead of a pointer to a fieldref), use '.*float ptr;', or you can define the field using a typedefed pointer.
Pointers can be used without qcc extensions, however you can only get the address of an entity's field, and you can only write. This is still sufficient to rewrite world's fields, but not particuarly useful otherwise.
It is not possible to pass the address of a local into a child function, due to qcvm limitations. As a work around, you can define the local as static or use the alloca intrinsic. Generally you should use __[in]out arguments instead
Classes can be prototyped with just 'class foo;', but any class-specific fields will not be usable until the actual class definition, which can mean that methods must have their code defined outside of the class itself.
A method can include only its prototype within the class, and with the eventual method being defined as eg: 'void() classname::methodname = {};'.
Accessors
accessors are a weird and whacky way to invoke functions in a more friendly way. They allow a reference/handle to provide a number of properties that invoke get or set functions when used. Many of these functions are excelent candidates for inlining...
A good example is that of setting up a string-buffer type that invokes an engine's bufstr_get/set builtins, allowing you to write stringbuffer code as if it were simply accessing an array.
index types can be any type of variable, so eg hash tables can be accessed as hashaccessor["foo"].
Here's an example using the string buffers extension:
accessor strbuf : float
{
inline get float asfloat[float idx] = {return stof(bufstr_get(this, idx));};
inline set float asfloat[float idx] = {bufstr_set(this, idx, ftos(value));};
get string[float] = bufstr_get; //we can get away with directly referencing existing functions/builtins too.
set string[float] = bufstr_set;
get float length = buf_getsize;
};
void() accessorexample =
{
strbuf b = (strbuf)buf_create(); //buf_create normally returns a handle in a float. which is unfortunate.
//We can now use b as if it were defined as string b[];
//There isn't even a limit to the indexes!
b[0] = "This is";
b[1] = "a test";
b[2] = "of stringbuffer access";
//gap!
b.asfloat[4] = 4;
//loop through them all. Note how the length property invokes buf_getsize which tells us the maximum valid index.
for (float i = 0; i < b.length; i++)
print(sprintf("%d: %s\n", i, b[i]));
//still needs to be freed though
buf_destroy((float)b);
};
An accessor property defined with an & after the access type specifies that the 'this' inside the code can actually be written to. Otherwise it should be considered const, which is fine in the above case where it is just a handle.
In set properties, the value to assign is simply called 'value'. If there is just a type with no name inside the array, then the used key will be named 'index'.
The index type can be anything, so long as it is typedefed.
Note that eg 'b.foo' is equivelent to 'b["foo"]' when b is a variable of type accessor and 'foo' is not a property of b, b has an unnamed property with an index type of string, and 'foo' isn't an immediate. This is useful with accessors built around hashtables.
The unnamed property can be a non-array too - such properties can be accessed only via eg '*b'.
Different engines support different extensions.
These take the form of added builtins, new fields with special side effects (or even existing fields with new values meaning new stuff), or globals that report additional results or change the behaviour of existing builtins.
Engine extensions can be useful but they can also restrict which engines your mod can run on. You'll need to find the right compromise for your mod yourself.
Most of the common important extensions can be queried at run time. This is done eg as following:
if (cvar("pr_checkextension"))
if (checkextension("FRIK_FILE"))
canopenfiles = TRUE;
Note that QuakeC does not normally early-out, so the two if statements must be nested and not replaced with an && operator.
FTEQW (and QSS) have a few builtins that have no formal named extension. These can be queried with eg the following:
if (cvar("pr_checkextension"))
if (checkextension("FTE_QC_CHECKCOMMAND"))
{
if (checkbuiltin(search_getfilemtime))
cancheckmodificationtimes = TRUE;
if (checkcommand("cef"))
canusewebbrowserplugin = TRUE;
}
Here's a list of the engines with extensive extensions. These files typically contain comments that describe the new stuff either on a per-feature basis or per-extension basis. If you don't understand the description of a feature for one engine then you may find another engine describes it with greater clarity.