Quake Strogganoff Lesson: Functions

Sat, Jan 18, 2020 5-minute read

So I thought I’d take a moment to cover some information concerning functions which have been living and farming in the four Farthings of the Shire for many hundreds of years. Quite content to ignore and be ignored by the world of the Big Folk… Er, wait. No.

Functions are the life blood of making cool stuff happen. Ever wonder why entities in Quake are named func_ anything? Corn? No, it’s functions! A door is a series of functions. So are weapons. Players. Even the world itself. Everything that happens is the result of the tireless work of functions (unless they get too tired and then you get a stack overflow). This won’t be an in-depth tutorial as there are already a multitude of resources out there, however I would still like to cover some basics as well as link off to resources that I’ve found very helpful in my journey as well.

First, some important QuakeC Functions:

The following is from the ‘QuakeC from Scratch’ tutorial which you can check it out here created by Ender.

worldspawn        - Called when the world.. umm.. spawns!
main              - Have no idea when this is called...
StartFrame        - Called at the start of each frame
ClientConnect     - Called when a client connected to the server
ClientDisconnect  - Called when a client disconnects from the server
ClientKill        - Called when a client issues the 'kill' command
PutClientInServer - Called to spawn the clients player entity
PlayerPreThink    - Called every frame, before physics.
PlayerPostThink   - Called every frame, AFTER physics.
SetNewParms       - Called on level change
SetChangeParms    - Called on level change

I would suggest giving these functions a good read as they provide the foundation for any project. I won’t go over variable types and such as the previously linked documentation covers this but I would like to demonstrate some function basics in QuakeC.

Function basics

How a basic function works in QuakeC:

void() CoolPrintFunction =
{
		dprint ("Hello there!");
		dprint ("\n");
}

NOTE: dprint will only show up is you have developer set to 1. Also check out sprint and bprint.

This function will print “Hello there!” in the console when it’s called. So if you put:

CoolPrintFunction();

Somewhere in the code, like at the bottom of PutClientInServer(), it should get called and print to the console when the player spawns.

This is also a fun way to also check if functions are being called and for more advanced (and useful) functionality you can print variables, coordinates, etc…

Function prototypes

Typically at or near the top of a file or just above a function which calls it. You might see a lot of this:

void() player_run;

This is due to the procedural nature of QuakeC, anything that calls an existing function BEFORE the function definition will result in an error because the compiler hasn’t found that function yet / has no idea what it is.

If you open up player.qc and look at the code around line 89, you get this code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void() player_run;

void()	player_stand1 =[	$axstnd1,	player_stand1	]
{
	self.weaponframe=0;
	if (self.velocity_x || self.velocity_y)
	{
		self.walkframe=0;
		player_run();
		return;
	}

...

};

void()	player_run =[	$rockrun1,	player_run	]
{
	self.weaponframe=0;
	if (!self.velocity_x && !self.velocity_y)
	{
		self.walkframe=0;
		player_stand1();
		return;
	}

	if (self.weapon == IT_AXE)
	{
		if (self.walkframe == 6)
			self.walkframe = 0;
		self.frame = $axrun1 + self.walkframe;
	}
	else
	{
		if (self.walkframe == 6)
			self.walkframe = 0;
		self.frame = self.frame + self.walkframe;
	}
	self.walkframe = self.walkframe + 1;
};

On line 9 in the example above you can see player_run(); is called, however, the actual function for void() player_run = isn’t defined until further down, at line 17.

If you were to comment out void () player_run; as seen above on line 1, it won’t lead to an error because there’s another void () player_run; in weapons.qc within the project files. And in checking the progs.src we can see that weapons.qc is BEFORE player.qc.

If you comment out line 4 in player.qc, you’ll get an error. But hopefully this example still makes sense. Bottom line. If you’re trying to call a function BEFORE it’s defined, you’ll need to add what can be thought of as a placeholder above it.

Passing information with functions:

So here’s a fun little function example that lets you specify an arbitrary amount of gibs by passing information between functions. Lets create a custom function called TossGibs which will pass on all the information ThrowGib needs as well as an extra bit of information for how many.

In QuakeC you have the function type void which is typically known for not returning anything.

void(string model, float dmg, float gibsamount) TossGibs =
{
		local float n;

		for (n = 1; n < gibsamount; n++) //start at 1 to avoid an extra gib
		ThrowGib (model, dmg);
}

And to call this function, like when an enemy is gibbed:

TossGibs ("models/objects/gibs/bone/tris.md2", self.health, 8);

In this function we simply pass on the model (string) and dmg (float) to ThrowGib but we use gibsamount (float) in a for loop to tell the function how many times to iterate through its loop (which essentially happens all at once) and in this case will produce 8 gibs.

This is the same as doing:

ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);
ThrowGib ("models/objects/gibs/bone/tris.md2", self.health);

Which is fine if you don’t want to have a different model for each gib but hopefully you can see the benefit of having a handy little function to automate repetitiveness and learned something about passing information into functions. And as bonus you can do something ridiculous like spawn 800 gibs from a corpse.

Lotsagibs

Happy modding!