EXT CSQC

From FTE
Jump to: navigation, search

(Copy and paste from http://web.archive.org/web/20070108102613/wiki.quakesrc.org/index.php/EXT_CSQC )

This specification is subject to change. Engines should not yet advertise or detect the extension key in public releases.

CSQC - or as engine modders call it: a good way to get other people to stop other people asking for new features - is a highly adaptable extension.

Feel free to place random comments all over the place. This isn't in use yet, so it's quite easy to change all this babble.

Major features:

   * Able to draw custom status bar.
   * Able to adjust models and stuff on models.
   * Prediction.
   * Able to change fov
   * Able to launch sounds localy
   * A brilliant starting point for umpteen extra extensions.

CSQC progs should be compiled into a csprogs.dat by default, but there's no reason why an engine can't provide cvars to use a different name. This must match the one present on the server to be considered valid. It doesn't have to of course, but it should do.

CSQC is intended to replace as much of the fixed-function client code as possible, without requiring in-depth knoledge of the engine's network protocol. Networking will be discussed later in the document. As far as drawing anything game related is concerned, the CSQC orders it all. Views, scoreboards and huds included. If the csqc were to simply return, you'd have nothing drawn. Areas that the CSQC fails to draw over will contain undefined pixels.

The CSQC code must export a CSQC_Draw function. Here's an example:

void() CSQC_Draw =
{
       CL_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 v1 compatability.
       CL_SetViewf(VF_MIN_X, 0);
       CL_SetViewf(VF_MIN_Y, 0);
       CL_SetViewf(VF_SIZE_X, cvar("vid_conwidth"));
       CL_SetViewf(VF_SIZE_Y, cvar("vid_conheight"));
       CL_SetViewf(VF_FOV, cvar("fov"));
//     CL_SetViewv(VF_ORIGIN, player_org);
//     CL_SetViewv(VF_ANGLES, player_ang);
       CL_AddEngineEnts();    //Add any entities that the csqc is not privvy to.
       CL_DrawScene();
       csqc_drawsbar();
};

As you can see, that's a basic example used by a mod which just wants to adapt it's status bar (actual sbar drawing code shown later). The structure allows csqc to draw it's own subwindows, secondary views and whatnot. The view is fully customisable, and the engine is free to add new view properties as it needs (eg: seperate fov_x/fov_y keys). The engine sets it's defaults inside the builtin CL_ClearScene.

(VERY)Simple sbar code:

void() csqc_drawsbar =
{
       local float health;
       if (enginesbar)
       {
               CL_!DrawSBar();
               return;
       }
       health = CL_GetStat(STAT_HEALTH);
       if (health < 20)
               CL_DrawPic("sbar/lowhealth.tga", 0, 480-64);
       else if (health > 80)
               CL_DrawPic("sbar/highhealth.tga", 0, 480-64);
       else
               CL_DrawPic("sbar/midhealth.tga", 0, 480-64);

};

Note: stats MUST match across engines, even if in an emulated sence. stats 0-31 are for engine maintaners to standardize. stats 32-128 are for QC modders to do as they choose on a per-mod basis. Refer to the 'clientstat' server builtin for how a server mod sends a custom stat.

Required engine cvars:

vid_conwidth
vid_conheight
       These cvars are used to tell the csqc the view dimensions. Everything is scaled to these.
       An engine is free to make them read-only if needed, though changing on the fly is always cool.

csqc builtins:

For the most part, the csqc vm should provide the same builtins as the ssqc. There are exceptions though, where ssqc builtins don't make sence. This is the list of the builtins specifically removed:

   * checkclient
   * stuffcmd
   * bprint
   * sprint (use 'print' instead)
   * aim
   * writebyte
   * writechar
   * wruteshort
   * writelong
   * writecoord
   * writeangle
   * writestring
   * writeentity
   * precache_file
   * changelevel
   * logfrag (qw)
   * infokey (qw)
   * multicast (qw)

3d scene management

void() R_ClearScene = #300;
       Clear the scene, reset the view values to default.
void(float mask) R_AddEntityMask = #301;
       Add all entities according to a drawmask (added if they match).
       Acts as if R_AddEntity was called for each one of them.
       For each entity added, the predraw of that entity function will be called before being any fields are read.
void(entity e) R_AddEntity = #302;
       Add a single entity to the scene. It's fields are copied out immediatly. This allows for repeated calling.
       Does NOT call the predraw function. This is useful for adding the entity within a predraw function from addentitymask without recursion. (think Quad shells).
float(float propertyindex, ...) R_SetViewFlag = #303;
       Change a property of the 3d view.
       Returns 0 if the property flag wasn't recognised, returns 1 if it applied. A future extension might mandate returning -1 on bad values.
       Extension property indexes can be added using the same ranges as for builtins.
       Valid parameters in any compliant engine:
       VF_MIN = 1 (vector)
       VF_MIN_X = 2 (float)
       VF_MIN_Y = 3 (float)
       VF_SIZE = 4 (vector) (viewport size)
       VF_SIZE_Y = 5 (float)
       VF_SIZE_X = 6 (float)
       VF_VIEWPORT = 7 (vector, vector)
       VF_FOV = 8 (vector)
       VF_FOVX = 9 (float)
       VF_FOVY = 10 (float)
       VF_ORIGIN = 11 (vector)
       VF_ORIGIN_X = 12 (float)
       VF_ORIGIN_Y = 13 (float)
       VF_ORIGIN_Z = 14 (float)
       VF_ANGLES = 15 (vector)
       VF_ANGLES_X = 16 (float)
       VF_ANGLES_Y = 17 (float)
       VF_ANGLES_Z = 18 (float)
       VF_DRAWWORLD = 19 (float)
       VF_DRAWENGINESBAR = 20 (float)
       VF_DRAWCROSSHAIR = 21 (float)
void(vector org, float radius, vector rgb) R_AddDynamicLight = #305;
       Adds a light to the scene with the specified properties.
void() R_RenderScene = #304;
       Invokes the renderer to actually draw the scene inside the call.
       Does not clear the display lists.
float(string s) precache_model = #20;
       Precaches a model for clientside usage. If the server qc already precached it, reuse that instead.
       Some engines support server-side precaching of additional models. Because the models need to be translatable by index, these engines must ensure that additional models do not overwrite the client models. It is therefore suggested that engine modders allocate cs models negativly.
vector (vector v) unproject            = #310;
       Converts from 2d-space to 3d-space. Uses the currently set viewport, origin, angles, and projection info from ?SetViewFlag.
vector (vector v) project              = #311;
       Converts from 3d-space to 2d space. Uses the currently set viewport, origin, angles, and projection info from ?SetViewFlag.

2d display Note that the vectors only use the first two componants, the third is ignored. With texture names, should the csqc leave off the filename extension, The engine should scan through all the extensions that it supports. The engine must support lmp files, and 32bit bottom up tgas should be supported, pngs and jpegs are just bonuses.

float(string name) CL_is_cached_pic = #316;
       returns true only if an image is already cached.
void(string name) CL_precache_pic = #317;
       precaches an image.
vector(string picname) CL_drawgetimagesize = #318;
       Retrieves the size of an image that the engine believes it to be.
       Note that image replacement on hud elements can do inaccurate things to image scale.
void(string name) CL_free_pic = #319;
       un-caches an image.
void(vector pos, float char, vector size, vector rgb, float alpha) CL_drawcharacter = #320;
       draws a characture using the quake font.
void(vector pos, string text, vector size, vector rgb, float alpha) CL_drawstring = #321;
       Draws a text string using the quake font. Size is per-characture.
void(vector pos, string picname, vector size, vector rgb, float alpha) CL_drawpic = #322;
       Draws an image.
void(vector pos, vector size, vector rgb, float alpha) CL_drawfill = #323;
       Draws a single block of colour.


float(float statnum) cs_getstatf = #330;
       Returns the client's value of a particular floating point stat. Note that this does not actually include any of the normal stats.
float(float statnum) cs_getstati = #331;
float(float statnum, float first, float count) cs_getstati_bits = #331;
       casts the engine stat to an int and returns it as a float.
       if first and count are used, retrieves only the bits specified and shifts down (this is used for sigils in the STAT_ITEMS stat)
string(float statnum) cs_getstats = #332;
       Strings are encoded into four consecutive stats and have a max length of 16 bytes.
       This will return a temp string.
void(entity e, float modelindex) CSQC_SetModelIndex = #333;
       Sets an entity's model according to a modelindex.
       This is useful when transfering modelindexes across the network but otherwise should not be used.
string(float modelindex) CSQC_ModelnameForIndex = #334;
       Retrieves the modelname from an assosiated modelindex.
void(float scale) cs_setsensitivityscaler = #346;
       Scales the user's sensitivity by the parameter.
       Useful for sniper zoom.
void(string s, ...) csqc_centerprint = #338;
       Directs the engine to draw centered text over the screen using it's regular code as if responding to the server.
       This is a convienience function.
void(string s, ...) print = #339;
       print text on the console and notifiction lines. Like dprint, but doesn't need developer set.
void(float effectnum, vector org, vector vel, float countmultiplier) cs_pointparticles = #337;
       Spawns particles in the style of an ef_efname effect.
       Doesn't play sound. Doesn't spawn a dynamic light.
       The normal 'particle' builtin also works, and should be used in that event.
void(float effectnum, entity ent, vector start, vector end) cs_trailparticles = #336;
       Spawns particles in the style of an ef_efname effect.
       Doesn't play sound. Doesn't spawn a dynamic light.
       The normal 'particle' builtin also works, and should be used in that event.
float(string efname) cs_particleeffectnum = #335;
       Returns an effect number which can be passed into any of the particle effect spawn functions.
       Returns 0 if the effect name was not recognised (or was not loaded/supported).
       Do not assume all engines will return the same values. Engines with fully scriptable effects will not do so, do not assume that the same engine will always return the same indexes either.
       These are the non-extended names that should be supported:
               te_explosion (rockets), te_blob (tarbaby), te_gunshot, te_wizspike, te_knightspike, te_spike, te_superspike.


float(float framenum) cs_getinputstate = #345;
       Reads viewangles and other properties into csqc's globals.
       Returns false if the framenum is out-of-date.
       This forms part of the prediction support.
       The servercommandframe global contains the last input frame ackoledged by the server, while clientcommandframe contains the most recent frame generated by the client.


void() cs_runplayerphysics = #347;
       Run's the engine's standard prediction algorithms.
       Can do nothing if the engine doesn't want to provide one (but must be present).
       Exact physics behaviour is up to the implementation. Behaviour should be consistant between alternate implementations of the same network protocol.
string(float playernum, string keyname) cs_getplayerkey = #348;
       Obtains various player specific things. Items that must be supported are:
       name, frags, bottomcolor, topcolor.
       Negative player indexes are interpreted as frag-sorted. -1 is the leaving player, -2 is the second best player.
       This is akin to quakeworld's infokey builtin with a player number.
float() isdemo = #349;
       Returns true if playing a demo.
float() isserver = #350;
       Returns true if the mod can communicate with the server via cvars and console commands and things.
string(float keynum) keynumtostring = #340;
       Retrieves the name of a key (as used by the bind console command)
float(string keyname) stringtokeynum = #341;
       Finds the key number of named keyboard/mouse/joystick button/key.
string(float keynum) getkeybind = #342;
       Obtains the full command that a button is bound to.
       To set the key, use localcmd with the bind console command.
float() ReadByte = #360;
float() ReadChar = #361;
float() ReadShort = #362;
float() ReadLong = #363;
float() ReadCoord = #364;
float() ReadAngle = #365;
string() ReadString = #366;
float() ReadFloat = #367;
       Reads a part of a network message.
       Note that this call is only valid when the engine directs the csqc to start reading. Valid only during that call.
       In the case of ReadString, it is returned within a temporary string.
       It is up to the engine exactly how big coords and angles are, so the csqc should ensure correct matching.
void(string str) RegisterCommand = #352;
       Registers a console command. Any commands that are registered will cause CSQC_?ConsoleCommand to be called any time the user tries using the command. The command should have top-most priority, above even aliases.
float() EntWasFreed = #353;
       Returns true if the entity was recently freed with the remove builtin, and hasn't been reused yet. Note that at the start of a map this builtin can be unreliable. Otherwise it should have 2 seconds to be reliable (before the entity is reused). This makes the physics code with touch/think functions easier.
void(vector org, vector forward, vector right, vector up) setlistener = #351;
       This builtin allows the csqc to position the 'listener'. This will affect the volume of sounds playing nearby. The directions are required for correct audio spacialisation (just use makevectors).

Network Protocol: The way csqc networking is designed, is to have the csqc entities seperate from the engine's entities. The entities are considered individual packets, thier length known only to the csqc. It is recommended that engines have a cvar for forcing packet size to be written, but this is not required except as a handy debugging tool. If the entity has a 'version' field set on the server, it is to be sent via the csqc transport, otherwise it goes over whatever transport the engine already has in place and is then never known to the csqc. Conceptually the csqc transport is a versioned one. New packets are typically only sent when the version on the server changes. However, due to packetloss, there are other times when a packet might be sent. The engine must ensure that a version of the entity arries at some point, either by sending reliably, spamming packets until one is acknoledged, or retransmitting only on a lost packet. The server and client qc MUST expect multiple read/write requests from the engine.

Server changes: The server's qc is mostly the same, though there are a few extensions as follows:

void(float index, float type, .void field) SV_AddStat;
       Set up an auto-sent player stat. Client's get thier own fields sent to them.
       Index may not be less than 32.
       Type is a value equating to the ev_ values found in qcc to dictate types. Valid ones are:
               1: string (4 stats carrying a total of 16 charactures)
               2: float (one stat, float converted to an integer for transportation)
               8: integer (one stat, not converted to an int, so this can be used to transport floats as floats - what a unique idea!)


.float(entity e, entity viewer) SendEntity;
       Field used to store which function is responsible for sending the entity data.
       If it returns false, the entity is not sent to that player. Hiding it if the client already has a version.
.float Version;
       This field contains the current version number. The ssqc is responsible for incrementing this when some transmitted field is changed.

The SendEntity function should issue writebytes and stuff to send the entity data to the client. This includes origin, angle and stuff. You can send whatever you wish in there, encoded however you see fit. It will be called by the server each time the Version value changes and is visible to the player.

Prediction, how it works: The client maintains a number of movement packets. Coceptually, it bounces them off the server. The echo it recieves is it's entity with the movement command applied to it. It also needs a sequence bounced too, which is performed by the engine (the ssqc never knows the sequence numbers), and the server only bounces it. So the client knows the last input state applied, and is able to reapply all the additional input to the servers state. This happens inside the csqc by the csqc requesting inputs and working on globals to figure out where the ent should be. Prediction errors are still likly so the csqc should apply some sort of iterpolation to slide the view to it's corrected position rather than snapping it. Input packets contain fixed info in the base specification where the csqc is expected to use clientcommands for anything more complex. Note that the sequence numbers could potentially be based on time rather than framerate

Differences between Quake and QuakeWorld: The csqc favours the Quake builtin behaviour. It does not use message levels in the engine, nor does it unconditionally print in the dprint builtin. It does use the QuakeWorld player physics model

CSQC entry points:

void() CSQC_Init;
       This QC function is called by the engine when the csqc is first loaded. Consider it like worldspawn.
void() CSQC_Shutdown;
       Called when the csqc is shutting down cleanly (not called if there was some sort of error).
void() CSQC_UpdateView;
       Called when the engine wants the csqc to update the player's view.
       This is called once a frame, and must update the entire screen.
       It must also draw huds and scoreboards.
void(string cmd) CSQC_Parse_StuffCmd;
       Called whenever the engine receives an svc_stuffcmd. Instead of executing it immediatly on the console, it is instead passed to the csqc for correct handling. This could be passed on to localcmd, for example (the string includes a new line, and could be multiple lines).
void(string text) CSQC_Parse_CenterPrint;
       Called when the client receives an svc_centerprint. The csqc either draws it's own menu in response, or passes it on to the cprint builtin.
void(string text) CSQC_Parse_Print;
       Called when the client receives an svc_print that has '\n' at the end. The csqc either draws it's own menu in response, or passes it on to the print builtin. NOTE: it MUST collect all svc_print messages until it gets '\n' for tokenizing and other things.
float(float event, float parama, float paramb) CSQC_InputEvent;
       event is one of: 0:key pressed, 1:key released, 2:mouse move.
       mouse movement is not yet notified. Call getmousepos to find where the cursor currently is.
       This is called if setwantskeys was last called with the parameter true.
       This func returns a value. "false" signalize that engine *should* take care of the keypress, a return value of true will make the engine otherwise ignore the event ever happened. This helps to use only keys that are really needed.
       With keyboard events, parama is set to the keycode. The key values are as in the menu.dat (and DarkPlaces), and consistant between engines.
       With mouse movement events, parama contains the X motion of the mouse, while paramb contains the Y motion of the mouse.
float(string cmd) CSQC_ConsoleCommand;
       This qc function is called when a command registered with the registercommand builtin is used.
void(float isnew) CSQC_Ent_Update;
       Called when an entity is recieved from the server. isnew is set if the engine spawned a new entity. The engine is responsible for assigning spawning entities, and must call with the same one, of course. The entnum field must be set before this is called for matching with the writeentity builtin on the server.

The entity reference is passed to the csqc in the traditional 'self' builtin.

void() CSQC_Remove;
       Called when an entity was removed/hidden on the server. The self global refers to the csqc's engine allocated entity (even if you removed it from the csqc already - be warned). The csqc is assumed to call the remove builtin inside this function (you should either remove or clear the entnum feld). If you want to leave gibs around, you're free to do so. Just fade them out inside the csqc please, or people will complain that it's too messy.

CSQC global variables: The list follows. This is more QC style than real documentation. These all do the same thing as on the server where names match. New globals are commented.

entity         self;
entity         other;
entity         world;
float          time;
float          frametime;      //time since last CSQC_UpdateView;
float          player_localentnum;     //the entnum
float          player_localnum;        //the playernum
float          maxclients;     //a constant filled in by the engine. gah, portability eh?
float          clientcommandframe;     //player movement
float          servercommandframe;     //clientframe echoed off the server
string         mapname;        //the map as found in the map command. Will not contain non-filename charactures. (expected to be used with fopen type things)
vector         v_forward, v_up, v_right;

// set by traceline / tracebox

float          trace_allsolid;
float          trace_startsolid;
float          trace_fraction;
vector         trace_endpos;
vector         trace_plane_normal;
float          trace_plane_dist;
entity         trace_ent;
float          trace_inopen;
float          trace_inwater;

//the following globals are set by cs_getinputstate //they are read by cs_runplayerphysics, if the engine supports that. //these fields are read and set by the default player physics

vector         pmove_org;
vector         pmove_vel;
vector         pmove_mins;
vector         pmove_maxs;

//retrieved from the current movement commands (read by player physics)

float          input_timelength;
vector         input_angles;
vector         input_movevalues;       //forwards, right, up.
float          input_buttons;          //attack, use, jump (default physics only uses jump)

CSQC Field Variables:

// // system fields (*** = do not set in prog code, maintained by C code) // .float modelindex; // *** model index in the precached list .vector absmin, absmax; // *** origin + mins / maxs

.float entnum; // *** the ent number as on the server .float drawmask; .void() predraw; .float renderflags; //See RF constants

.float movetype; .float solid;

.vector origin; // *** .vector oldorigin; // *** .vector velocity; .vector angles; .vector avelocity;

.string classname; // spawn function .string model; .float frame; .float skin;

.vector mins, maxs; // bounding box extents reletive to origin .vector size; // maxs - mins

.void() touch; .void() use; .void() think; .void() blocked; // for doors or plats, called when can't push other

.float nextthink;

.entity chain;

.entity enemy;

.float flags;

.float colormap;

.entity owner; // who launched a missile

CSQC Constants:

renderflags field uses these: float RF_VIEWMODEL = 1;

       The entity is never drawn in mirrors. In engines with realtime lighting, it casts no shadows.

float RF_EXTERNALMODEL = 2;

       The entity is appears in mirrors but not in the normal view. It does still cast shadows in engines with realtime lighting.

float RF_DEPTHHACK = 4;

       The entity appears closer to the view than normal, either by scaling it wierdly or by just using a depthrange. This will usually be found in conjunction with RF_VIEWMODEL

float RF_ADDITIVE = 8;

       Add the entity acording to it's alpha values instead of the normal blend

float RF_USEAXIS = 16;

       When set, the entity will use the v_forward, v_right and v_up globals instead of it's angles field for orientation. Angles will be ignored compleatly.
       Note that to use this properly, you'll need to use the predraw function to set the globals, or to add the models individually.