The Quake command system consists of 2 buffers (text storage memory):This is a boring topic, it is also the primary backbone of the Quake engine. There are other sub-systems that are important, but this is the one that makes the Quake engine flexible and powerful.
Memory could be done another way, game logic could be Quake 2 DLL style or hardcoded, but everything in the engine depends on this to give it flexibility. It also makes the engine very easy to expand, gives users many tools, make everything configurable.
Both of the above are essentially plain text. When you start up Quake, more or less config.cfg gets read and copied into the command execution buffer. When you type something in the console, that line gets added to the command execution buffer. When you type "exec myconfig.cfg", that file is tossed into the command execution buffer.1. The command execution buffer. Commands waiting to be processed. (8192 bytes in stock Quake)
2. The console. Output from commands already processed. (16384 bytes in stock Quake, almost always larger in a modified engine)
Each frame -- a frame being every time the engine runs one cycle of code --- the engine will run everything in the command buffer one line of text at a time until there is no more text to process. The exception is the "wait" command, which will tell the engine to stop processing commands for this frame.
In code, the way the command buffer works is this:
1. Commands. Essentially a mapping of text to a function. Think of it as a 2 column table. key = "path" function = COM_Path_f (code address).
There is no set number of commands. It throws every command on to the hunk (the memory Quake allocates for operation) during engine start up and alphabetizes it as each command is added. Since after engine startup (host initialization), the engine bookmarks a point in memory and says "everything after this will be cleared every map" and as a result, no new commands can be added when host initialization, and therefore the memory bookmark of the "clearing point", is set.Code: Select all
Text Function (char * name) void (*xcommand_t) (void) ----------------------- ---------------------------- "centerview" V_StartPitchDrift "map" Host_Map_f "path" COM_Path_f "quit" Host_Quit_f
2. Console variables (cvars). Essentially a mapping of text to a function. Think of it as a 3 column table. key = "volume" string representation of value = "1.0" machine representation = (floating point stored interpretation of value). A cvar can't have the same name as a command.
Any time a console value is changed, the floating point value is recalculated from the text. So you do "cl_forwardspeed 400", the "400" is evaluated to be the number 400.0 in floating point --- which is the data type that supports decimal places. Cvars in Quake are fixed and unchangeable and known when compiling the engine source code, so unlike commands they don't go on the Hunk (but the string value does).Code: Select all
Cvar Name Text Text Value Floating Point Numeric Value of Text ----------------------- ----------- --------------------------------------------- "cl_forwardspeed" "200" 200.0 "name" "player" 0 (since "player" as text doesn't convert to a numeric value) "volume" "0.7" 0.7
3. An alias. When found on the command buffer, it is expanded to its full value .... alias +quickattack "impulse 7; +attack; impulse 2" ... and interpreted.
4. Command parser. Takes a line of text [or up to a semi-colon] like ---> "maxplayers 16" . Splits on spaces (argument #1 "maxplayers" argument #2 "16" ...) and counts the number of arguments (1: "maxplayers" 2: "16" <--- count = 2) and as commands are executed, the function checks the argument count and can check the different argument values if the command uses them or ignore them if the command doesn't use it.
The command might refuse to do anything if the arguments it requires aren't available and might print a message complaining and explaining how to use the command.
The command parser splits on spaces and a few other characters, actually. Notably, if part of current line is within quotes like if the current line is: name "john smith" is doesn't split on a space in quotes (Argument #1 "name" Argument #2 "John Smith").
5. The special command "cmd". Instead of interpreting this locally, sends it to the server you are connected to (which may or may not choose do anything with what you sent depending on the command). "rcon" --- remote console -- operates in a similar manner, but isn't actually part of the stock [Net]Quake engine
6. Executing a command on behalf of a client (clc_stringcmd) --- this is a curveball. You are connected to a server playing coop (the server might be another computer on a LAN) and type "god". Well ... this is a bit involved ...
Flow a remote command using 'noclip' or 'god' as examples wrote: 1. These commands in their code know they are only valid in-game2. Functions that sends the command to the server.Code: Select all
void Host_Noclip_f (void) { if (cmd_source == src_command) <---- "src_command" means came from the local command buffer (i.e. you typed in the console or pressed a key) { Cmd_ForwardToServer (); <-------- Even in single player, you have to be "connected to a server" (in single player, you are the server too). return; } .... }
3. Server reads message (host_client is a global, it is the current player we are getting messages from ...)Code: Select all
void Cmd_ForwardToServer (void) { if (cls.state != ca_connected) <------------- you need to be connected to a server (single player, you are the server too). { Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0)); return; } if (cls.demoplayback) return; // not really connected MSG_WriteByte (&cls.message, clc_stringcmd); <----- write the command out in the message we send to the server if (Q_strcasecmp(Cmd_Argv(0), "cmd") != 0) <----- "cmd" isn't something we send, it is something we ignore. We are "in-cmd" (see above "cmd") { SZ_Print (&cls.message, Cmd_Argv(0)); SZ_Print (&cls.message, " "); } // A modified Quake engine might have "talk macro expansion right here" if (Cmd_Argc() > 1) SZ_Print (&cls.message, Cmd_Args()); <------ sent the command arguments if there are any else SZ_Print (&cls.message, "\n"); <----------------- hrm? This is obvious, but why not present in other "if" condition }
4. Command is run, noting it is being applied as the current client ...Code: Select all
/* =================== SV_ReadClientMessage Returns false if the client should be killed =================== */ qboolean SV_ReadClientMessage (void) { ... case clc_stringcmd: <--------------- we received a command from a client s = MSG_ReadString (); <------------- read it in if (host_client->privileged) <------------- if special client, we do this command no matter what (idgods) ret = 2; else ret = 0; (check to see if we know what this command is) if (Q_strncasecmp(s, "status", 6) == 0) ret = 1; else if (Q_strncasecmp(s, "god", 3) == 0) ret = 1; (check to see if we know what this command is) if (ret == 2) Cbuf_InsertText (s); <------------------ idgods, run this command like we typed in the console. else if (ret == 1) Cmd_ExecuteString (s, src_client); <-------------- run this command noting this is for a specific client else Con_DPrintf("%s tried to %s\n", host_client->name, s); <----- we don't know what this command is, log it break; }
Code: Select all
void Host_God_f (void) { ... if (pr_global_struct->deathmatch && !host_client->privileged) <-------- Don't let someone do this if progs is in deathmatch mode. return; sv_player->v.flags = (int)sv_player->v.flags ^ FL_GODMODE; <---- modify player if (!((int)sv_player->v.flags & FL_GODMODE) ) SV_ClientPrintf ("godmode OFF\n"); ... }
Code: Select all
Memory Block (The Hunk)
Command buffer (our 8192 sized place to text to process)
Commands (The above table of text/function pairs)
Network connection status table (1 per max players, which is as many as 16)
String Memory (by default 256 KB)
-------------------
BOOKMARK --- *Clear Everything after here on new map* (host_hunklevel)
-------------------
Server progs.dat (compiled QuakeC game logic, interpreted as byte code)
Server QuakeC interpreter fields stuff (QuakeC can add fields per entity, the engine doesn't know in advance what these are)
Map data (the .bsp file)
Anything else