Enjoy your mug, I've seen worse...
* 10 byte length ints
Worse in QuakeC: no ints AT ALL, just floats, vectors, strings and entities.
* no bool short circuiting
Same in QuakeC. Shortcut bool is a non-standard compiler option, though.
* no built in list comparison function
Same in Perl and QuakeC.
* ["a"] == ["b"] return true, since it only checks for length
Worse in Perl:
"3" != "4"
"3" == "03"
"8" == "010"
"foo" == "bar"
"nanosecond" != "nanosecond"
Not an issue in QuakeC, as QuakeC has no lists (available as compiler extension though... and then there is no way to compare them either).
* one function per type to retrieve an element from a list (there is no generic one)
You don't want to know how fteqcc emulates arrays in the non-array-supporting QuakeC VM.
* lack of structs or other constructs
Same in QuakeC, there is one global type "entity", that's all. You can add fields to it anywhere in the code, though.
* function parameters are always passed by value (even lists)
Actually, ALMOST the same in QuakeC. A function can also take a field as an argument (which is a "pointer to a member" in C++ speak):
void(entity e, .float f) increase_field = {
e.f = e.f + 1;
}
Also, entities are never copied, and neither are strings (but they are immutable).
* the best way to append a element to a list is:
QuakeC has no built-in lists, just fixed size arrays... see below for an even worse (but today fixed) problem in QuakeC.
* its compiled to bytecode to a maximum of 16K per script, but it has no optimizing compiler
The only "optimizing" QuakeC compilers generate incredibly broken code when actually enabling them, but there is no real size limit in today's Quake engines.
* no support for lists of lists, or multidimensional arrays: so the suggested way is to use a so called strided list
Same in QC with "emulated" lists.
* even the most basic list manipulation functions aren't O(N)
In QuakeC with FTE extensions, accessing the n-th list element of an array is O(log n). Can't get any worse than that.
Now, to add my own WTF: QuakeC's string handling.
Most builtin functions just return pointers to static storage of the function. Thus, calling that function again will change the value of the first string!
string s;
s = ftos(1); // s is now "1"
dprint("two is ", ftos(2), "\n");
dprint("s is now ", s, "\n"); // s is now 2 (it is a reference to ftos's static string)
Later, some engines introduced an extension to allow certian functions use one of a buffer of eight static strings in a cyclic manner, to allow a bit more string manipulation easily:
while(...)
{
mytemp = strcat(mytemp); // revive mytemp
// now do something else with strcat (at most 7 calls, or it will destroy mytemp!)
}
DarkPlaces fixes that by an extension called DP_QC_UNLIMITEDTEMPSTRINGS which always allocates return value strings from a temporary buffer, which gets cleared when the game engine's call to the QuakeC function is over.
In any case, to make a string hold longer (it then is kept until the current round of the game ends), you can pass it to strzone():
string s;
s = somethingtemporary;
self.someproperty = strzone(s);
// 3 minutes later
dprint("my someproperty is ", self.someproperty, "\n");
Care has to be taken to not cause memory leaks that way; although strings are freed at the end of the match, one can easily have a memory leak that allocates hundreds of them in a frame. There is a function called strunzone() that can free strings, though... and this spawned a special function to change a string field of an object so it can persist:
void(entity e, .string field, string value) setstringproperty =
{
if(e.field != "")
strunzone(e.field);
if(value == "")
e.field = "";
else
e.field = strzone(value);
}
When the entity is to be freed, one has still to take care to strunzone() all its strings...