EXT CSQC

From Quake Wiki

Introduction

QuakeC code is normally run only on the server as a progs.dat file. Engines which support the EXT_CSQC extension run QuakeC code on the client. This enables the mod to control certain extra features:

  • Customize the status bar.
  • Adjust models and draw extra things on models (even with only one entity on the server).
  • Implement prediction.
  • Override the FOV.
  • Launch sounds locally.
  • Implement what would otherwise require another QSG extension.


By standard, CSQC should be compiled into and loaded from a csprogs.dat. Engines can provide cvars to specify a different name but that isn't part of this extension.


EXT_CSQC is intended to replace as much of the fixed function engine code as possible, without requiring knowledge of the engine code mechanisms and in a way that is consistent with the existing style of QuakeC.


The extension defines the whole csprogs, as well as adding some builtins and constants on the server.

Header Code

On the server:

// EXT_CSQC
// AddStat
void(float index, float type, ...) AddStat = #232; // third parameter is an entity field
float AS_STRING          = 1;
float AS_FLOAT_TRUNCATED = 2;
float AS_FLOAT           = 8;
// Entity Sending
.float(entity viewer) SendEntity;
.float Version;
float MSG_ENTITY = 5;
// Effect bit specifies that the entity is always to be sent to CSQC (provided it has a
// SendEntity function and Version is incremented. This is the same bit as used
// in DP_EF_NODEPTHTEST, but CSQC requires it even if the extension is not present.
float EF_NOPVSCULL = 8192;

On the client, EXT_CSQC System Globals Fields and Builtins List lists the core code. The following sections in this article explain their use.

Overview

There are two major design factors to be aware of first when beginning to understand Client Side QuakeC:

First is that the render function overrides the entire process of rendering. That is not to say that the CSQC replaces the low level rendering process - the engine provides functions for the CSQC code to call as the modder desires. However, if the CSQC rendering code does not do anything, then nothing will be rendered.

Secondly, CSQC does not automatically recieve any of the engine entities, even the rendering entities that the local client recieves. This acts as cheat protection, preventing players from making a client-side-only radar mod and such. The server mod must explicitly send the entities to the client as described later. It is also an important note that the server entities must still be linked into the world and viewable by a client in order to be sent to it.

Client Programs Entry Points

CSQC_Init

void() CSQC_Init;

This is called when the csprogs.dat is loaded into memory. It is here that sounds, models and images can be precached with precache_sound precache_model and precache_pic.

CSQC_Shutdown

void() CSQC_Shutdown;

This function is called before the csprogs is fully unloaded, both for shutdown and map change. It is possible to store data in cvars to read in after a map change in CSQC_Init, which is feasible in conjunction with the DP_CON_SET extension.

CSQC_InputEvent

float(float event, float key, float ascii) CSQC_InputEvent;
float EVENT_KEYDOWN = 0;
float EVENT_KEYUP = 1;
float EVENT_MOUSEMOVE = 2;

This function is called on keyboard and mouse events. The event parameter is one of the EVENT_ constants. For keyboard input the key parameter is the quake key code of the key that was pressed or depressed while ascii is the printable character. Potentially, ascii might be a full unicode char if the engine supports that, and is often 0 on keyup events or keydown events where escape or modifier keys etc were used. For mouse input the 'key' parameter contains the x movement, while the ascii parameter contains the y movement, but in DP, you may be required to use the vector() getmousepos builtin instead.

The function returns FALSE if the engine should process the event, or TRUE if the engine should ignore it.

CSQC_UpdateView

void(float swidth, float sheight) CSQC_UpdateView;

This function overrides the engine drawing code. It orders the rendering of everything including views, scoreboards and huds. If the csqc were to simply return, nothing would be drawn. Areas that the function fails to draw over will contain undefined pixels.

Example code:

void(float swidth, float sheight) CSQC_UpdateView =
{
    R_ClearScene();       //wipe the scene, and apply the default rendering values.

    //These lines, if used, specifiy various global view properties to the engine.
    //The engine returns 0 if it accepted the command, and -1 if it had an error.
    //The names listed here are the basic set required for basic compatability.
    R_SetView(VF_MIN_X, 0);
    R_SetView(VF_MIN_Y, 0);
    R_SetView(VF_SIZE_X, swidth);
    R_SetView(VF_SIZE_Y, sheight);
    R_SetView(VF_FOV, cvar("fov"));
    R_SetView(VF_ORIGIN, player_org); //if absent and the player's entity is sent via the regular protocols, the default is the player's entity origin
    R_SetView(VF_ANGLES, player_ang); //the default is the client's current view angle, plus idlescale stuff.
    R_SetView(VF_DRAWWORLD, 1);
    R_SetView(VF_DRAWCROSSHAIR, 1);
    R_SetView(VF_DRAWENGINESBAR, 0);

    // Add any entities that the csqc does not know about
    R_AddEntities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS);

    R_RenderScene();        // draw the view

    csqc_drawsbar();       // call to a custom function drawing the status bar
 };

R_SetView Parameters:

Flag parameters description
VF_MIN vector Top-left corner of the viewport. Only _x and _y are used.
VF_MIN_X float Left edge of the viewport.
VF_MIN_Y float Top edge of the viewport.
VF_SIZE vector Width and height of the viewport. Only _x and _y are used.
VF_SIZE_X float Width of the viewport.
VF_SIZE_Y float Height of the viewport.
VF_VIEWPORT vector, vector Origin and size of the viewport.
VF_FOV vector Field of view. Only _x and _y are used. Units are degrees for perspective projection.
VF_FOVX float Horizontal field of view in degrees.
VF_FOVY float Vertical field of view in degrees.
VF_ORIGIN vector The position of the camera.
VF_ORIGIN_X float
VF_ORIGIN_Y float
VF_ORIGIN_Z float
VF_DRAWWORLD float TRUE if the world model (bsp, all submodels and world entities and effects) are to be drawn, else FALSE. Default is TRUE.
VF_DRAWENGINESBAR float TRUE to draw the engine status bar (scoreboard, intermissions etc.), FALSE if you want to override it with your own code. Default is TRUE.
VF_DRAWCROSSHAIR float TRUE to draw the engine crosshair, FALSE to hide it or draw your own.

CSQC_ConsoleCommand

float(string s) CSQC_ConsoleCommand;

This function parses console commands entered on the client. The parameter s contains the entire string entered on the console.

The function must return TRUE if it parses the command or FALSE if it does not. Returning false causes the command to then be parsed by the engine, and will display an error message if the command is then not recognised.

Since the function is called before any engine parsing it is possible to override engine commands (for example +showscores and -showscores) which would be useful in building a custom status bar and scoreboard.

Sending Data to the Client

Stats

On the server

The AddStat builtin function is used to specify fields which will be automatically sent to the client whenever they change. This is done upon initialisation - it works in worldspawn().

void(float index, float type, .void field) AddStat = #232;

The index parameter is the index of the stat. Numbers 0-31 are reserved by the engine, so this parameter must be 32 or higher.

The type parameter is one of:

  • AS_FLOAT_TRUNCATED - sends the integer part of a float in a packed form. The field parameter must be a float.
  • AS_FLOAT - sends the whole float without any data loss. The field parameter must be a float.
  • AS_STRING - sends the first 16 characters of a string, requiring the space of 4 consecutive stats. The field parameter must be a string.
On the client

The function corresponding to the type parameter is used to retrieve the data, with the index corresponding to that of the server.

float (float statnum) getstat_float = #330;
float (float statnum) getstat_float_truncated = #331;
string (float statnum) getstat_string = #332;

Entities

On the server

The SendEntity function is used in conjunction with the .Version field. The .Version field should be incremented every time an update is desired. This causes the entity's SendEntity function to be called for every client that the entity is viewable to:

.float(entity viewer) SendEntity;

In this function, the entity which is being sent is self.

The viewer parameter is the client entity to whom the entity is being sent.

The return value should be TRUE if the entity is to be sent to that client, and FALSE if it not to be.

Inside this function, the core builtins WriteCoord, WriteAngle, WriteByte, WriteShort, WriteLong are used with the destination parameter MSG_ENTITY, indicating that the data is part of a CSQC entity update.

On the client

When an entity is received the function CSQC_Ent_Update is called. Here the CSQC core functions ReadCoord, ReadAngle, ReadByte, ReadShort, ReadLong are used to retrieve the data sent by the server. Any Writes that the server used must exactly match the Reads used by the client. No more, no less. The order must match too. You can use flags to conditionally send+receive fields.

void(float isnew) CSQC_Ent_Update;

If the entity does not yet exist it is created on the client and the isnew parameter is TRUE, otherwise FALSE. Basically, isnew says that the client called spawn() for you. The server entity may have previously been visible, just temporarily left the view.

The entity being created or updated is self. You would normally set the entity's drawmask to MASK_NORMAL and set or read a model/modelindex. You will need to animate and interpolate or predict the entity yourself.


If the entity is removed from the server it will also be removed from the client. The function CSQC_Ent_Remove is called on the client:

void() CSQC_Ent_Remove;

In this function, self is the entity to be removed, though it will not be removed automatically by the engine. This allows the mod to clean up any client-created entities associated with the one being removed. You will need to do remove(self) at some point, perhaps via a timer (useful if pvs culling is not in use for this entity).

Example Code
Please Note

Entity updates using this method are transmitted unreliably, meaning that there is no guarantee that a particular update may arrive before the next one supercedes it. This means each update should send the whole amount of data required - it is not possible to send some data as a delta of previously sent information. This is not so much of a problem because this extension allows far fewer updates to be sent.

Rendering Entities on the Client

Spawn your entity:

self = spawn();

Set a model/modelindex:

setmodel(self, "progs/player.mdl");

Set an origin:

setorigin(self, '0 0 0');

Set the drawmask to something that matches your call to addentities:

self.drawmask = MASK_NORMAL;

The entity is now visible.

Or you could do as above, but ignore the drawmask part and call:

addentity(self);

Rendering 2D HUD on the Client

An example can be found here: http://fteqw.svn.sourceforge.net/viewvc/fteqw/trunk/quakec/csqctest/src/cs/hud.qc?view=markup