There isn’t much documentation about client-side QuakeC, so I’ll add my 5 cents.
Note: CSQC is probably not for complete newcomers. If you’re a newcomer to QC, please go and learn how to make the rocket launcher shoot gibs first. It’s the traditional route.
I’ll dive right in.
Your CSQC code isn’t terribly different from any other QC, only it gets compiled (with fteqcc) into a file called csprogs.dat instead of progs.dat. It may have a defs.qc, or it may not have one. It will usually contain a file that’s dumped from the engine, named something like csplats.qc or fteextensions.qc, which contains declarations of all the relevant builtins and variables. This can act a little like a defs.qc does in SSQC. The fteextensions.qc file can be dumped from FTE or – easier – downloaded here.
On top of that, all your CSQC really needs are a handful of common functions.
Be aware that FTE allows you to just include the ones you actually need, while Darkplaces expects certain functions to be present as stubs even if they’re not used.
Note that your csprogs.src (or whatever you decide to call it) should contain a line saying
in order to include CSQC specific parts of fteextensions.qc. If your SSQC progs.src includes fteextensions.qc as well (like mine does) then the progs.src should contain
if it is a netqake/normal quake progs.
To compile first your progs.src and then your csprogs.src in one go, you can add the following line at the end of progs.src:
#pragma sourcefile csprogs.src
This is assuming you use fteqcc, of course.
Common CSQC functions
At the very least, your CSQC is supposed to render the current scene and likely the weapon viewmodel. (If you were modding Quake, you would also want it to render the status bar.) This is all done in one of the most common CSQC functions, CSQC_UpdateView(). You can simply write your own version of this function, and the engine will run it once every frame.
void (float vwidth, float vheight, float notmenu) CSQC_UpdateView;
You can put all kinds of stuff in this function, such as calls to draw your HUD, draw your mouse cursor, update your stats from the server, or whatever else your code should be doing each frame. There are only a few things that really should be in there:
This will make the scene render from scratch every frame.
- setviewprop(VF_DRAWWORLD, 1);
This sets the drawworld flag, so when the scene is rendered, the world is included as part of it.
- setviewprop(VF_DRAWCROSSHAIR, 1);
Sets the drawcrosshair flag.
- setviewprop(VF_DRAWENGINESBAR, 1);
Sets the drawenginesbar flag (Quake modders might want this.)
- addentities(MASK_ENGINE | MASK_VIEWMODEL | MASK_NORMAL);
Adds relevant entities into the scene, such as your weapon model or any client side entities with a matching self.mask field.
Makes it render out the scene for this frame.
The arguments (vwidth, vheight) are quite useful, mostly because they tell you the actual current screen width and height. You should write those into global variables because you’ll need them for a number of other things (such as bounding the mouse cursor or calculating the positions for GUI elements.) You should also check every frame if the screen width and height changed, because if it does, you’ll need to recalculate all your GUI positions (which probably rely on screen dimensions.)
And that’s it. This function alone should be enough to get a working CSQC progs in FTE (meaning one that actually draws the game), while DP will need a few extra stubs.
float (float evtype, float scanx, float chary, float devid) CSQC_InputEvent;
Here is where it gets interesting. This function allows the CSQC to intercept player input – mouse movement, key presses, mouseclicks. Almost any CSQC mod will use this in some form. As usual, you can write your own version, depending on what your code should actually do with input events.
Scout’s Journey uses this for example to intercept the TAB key and toggle the CSQC GUI in such a case. It also intercepts the Pause key. On top of that, it grabs the mouse whenever the CSQC GUI is active and updates the mouse cursor position every frame.
Note: This function is a float! It should return 1 if CSQC will handle an event, 0 if the engine should handle it. So whenever your code grabs a key press, do return 1.
Keys are detected via scancodes. A list should be inside fteextensions.qc.
This function’s arguments are worth a look. Let’s see:
evtype is a number. 0 = keydown; 1 = keyup; 2 = relative mouse movement; 3 = absolute mouse movement. You can use evtype to detect what kind of event you are dealing with. Keydown is clear enough; this means a key was pressed. Keyup means key released. Relative mouse movement is what you want to update your mouse cursor position. Absolute mouse movement, to my knowledge, is for touchscreens etc.
With keypress/keyup, this will be your scancode (key number.) This tells you which key was pressed.
With mouse movement, this is the movement along the X axis.
With mouse movement, this is the movement along the Y axis.
This tells you which device the event is from, in the case of multiple keyboards, multiple mice or multitouch devices.
Here are a few common things to do in this function:
- if (evtype == 0)
Checks if any key is pressed.
- if (scanx == K_TAB)
Checks if it’s the TAB key. The constants are in fteextensions.qc.
- if (scanx == K_MOUSE1)
Checks for left mouse button.
- if (evtype == 2)
Checks for mouse movement.
- cursor_pos_x += scanx;
Adds X axis movement to a (vector) variable holding the cursor position. This would be prudent if you have some custom function that draws a mouse cursor.
- cursor_pos_y += chary;
Same for Y axis movement.
Remember to bounds check the cursor position using the screen size from CSQC_UpdateView. Basically check if the cursor pos is outside the current screen size, and if so, set it to a valid position.
void(float apiver, string enginename, float enginever) CSQC_Init;
This function is simply called by the engine when the CSQC is loaded. Anything that should be done right at the start can go in here. An example would be to initialize an array or something (Scout’s Journey initializes its clientside loot table here.) You can leave this out if you don’t need it in FTE, but DP requires this as a stub.
void () CSQC_Shutdown;
The engine will call this at shutdown. DP requires this as a stub.
float (string cmd) CSQC_ConsoleCommand;
Parses console commands that have been registered with registercmd. DP expects this to be present and return 0, at least.
I thank Spike, Lord Havoc, gnounc, Urre and avirox for various bits of CSQC knowledge. I also thank early CSQC adopters like Ender, Dresk and Arkage.