## IndexPart I - Purpose and system explanation Part II - Implementing the Extension System into the Quake engine Part III - How to use the Extension System in QuakeC ## Part I - Purpose and system explanationMany engines provide new functionalities for QuakeC coders to use in their addons. But the problem for QuakeC coders is how to determine during run-time if the engine supports all the needed new functionalities? That is where LordHavoc came in. He added a builtin function which states if an extension is available in the engine or not. LordHavoc's function was added to the QSG standard by using the Enhanced BuiltIn Function System (EBFS) with the name "extension_find" and the number #99. If you want to add LordHavoc's "extension_find" to your own engine, you should add the EBFS before (tutorial here: EBFS.TXT). This tutorial will also make your engine compatible with LordHavoc's original system in DarkPlaces, which was done before the EBFS was created, where the QuakeC coder checks for the cvar PR_CHECKEXTENSION to determine if the builtin function "extension_find" #99 is available. So for checking builtin functions it is recommended to use EBFS's "builtin_find" and for all other enhancements LordHavoc's "extension_find". ## Part II - Implementing the Extension System into the Quake engineAdding the Extension System to your engine is very easy, even if you did lots of changes to your own engine. The new builtin function needs some new data: the names of the supported engine extensions. The main function loops through the array and compares the strings with the requested extension name. The builtin function "extension_find" uses the above function and passes the result to the PROGS.DAT. First we have to globally define the builtin number of "extension_find" in PROGS.H for later use across several files... #define PR_DEFAULT_FUNCNO_EXTENSION_FIND 99 // 2001-10-20 Extension System by LordHavoc At the top of PR_CMDS.C add the following code... // 2001-10-20 Extension System by LordHavoc start char *pr_extensions[] = { // add the extension names here, syntax: "extensionname", }; int pr_numextensions = sizeof(pr_extensions)/sizeof(pr_extensions[0]); qboolean extension_find(char *name) { int i; for (i=0; i < pr_numextensions; i++) { if (!Q_strcasecmp(pr_extensions[i], name)) return true; } return false; } /* ================= PF_extension_find returns true if the extension is supported by the server float extension_find(string name) ================= */ void PF_extension_find (void) { G_FLOAT(OFS_RETURN) = extension_find(G_STRING(OFS_PARM0)); } // 2001-10-20 Extension System by LordHavoc end ... and add this to the pr_builtin[] array (EBFS!) ... { PR_DEFAULT_FUNCNO_EXTENSION_FIND, "extension_find", PF_extension_find }, // 2001-10-20 Extension System by LordHavoc The Extension System is now ready to run, but we want it to be compatible with LordHavoc's original DarkPlaces implementation. So at the top of PR_EDICT.C add a new cvar called PR_CHECKEXTENSION and register it in PR_Init()... cvar_t pr_checkextension = {"pr_checkextension", "0", false, false}; // 2001-10-20 Extension System by LordHavoc (DP compatibility) Cvar_RegisterVariable (&pr_checkextension); // 2001-10-20 Extension System by LordHavoc (DP compatibility) Now we just have to initialize and set it accordingly when a PROGS.DAT has been loaded in PR_LoadProgs (EBFS!). Here are the changes (this code was outcommented in the EBFS tutorial)... // create builtin list for execution time and set cvars accordingly Cvar_Set("pr_builtin_find", "0"); Cvar_Set("pr_checkextension", "0"); // 2001-10-20 Extension System by LordHavoc (DP compatibility) for ( j=1 ; j < pr_ebfs_numbuiltins ; j++) { if (pr_ebfs_builtins[j].funcno) // only put assigned functions into builtin list { pr_builtins[pr_ebfs_builtins[j].funcno] = pr_ebfs_builtins[j].function; } if (pr_ebfs_builtins[j].default_funcno == PR_DEFAULT_FUNCNO_BUILTIN_FIND) { Cvar_SetValue("pr_builtin_find", pr_ebfs_builtins[j].funcno); } // 2001-10-20 Extension System by LordHavoc (DP compatibility) start if (pr_ebfs_builtins[j].default_funcno == PR_DEFAULT_FUNCNO_EXTENSION_FIND) { Cvar_SetValue("pr_checkextension", pr_ebfs_builtins[j].funcno); } // 2001-10-20 Extension System by LordHavoc (DP compatibility) end } A nice addition is the new command "extensionlist", which lists all available extensions. First you have to declare the new data globally in PROGS.H with these lines ... // 2001-10-20 Extension System by LordHavoc start extern char *pr_extensions[]; extern int pr_numextensions; // 2001-10-20 Extension System by LordHavoc end Then put the command function at top of PR_EDICT.C... // 2001-10-20 Extension System by LordHavoc start void PR_Extension_List_f (void) { int i; char *partial; int len; int count; if (Cmd_Argc() > 1) { partial = Cmd_Argv (1); len = strlen(partial); } else { partial = NULL; len = 0; } count=0; for (i=0; i < pr_numextensions; i++) { if (partial && Q_strncasecmp (partial, pr_extensions[i], len)) { continue; } count++; Con_Printf ("%s\n", pr_extensions[i]); } Con_Printf ("------------\n"); if (partial) { Con_Printf ("%i beginning with \"%s\" out of ", count, partial); } Con_Printf ("%i extensions\n", i); } // 2001-10-20 Extension System by LordHavoc end ... and at last add this function as a command in PR_Init() with the following line... Cmd_AddCommand ("extensionlist", PR_Extension_List_f); // 2001-10-20 Extension System by LordHavoc Now recompile the engine. Last but not least, do not forget to mention the Extension System in your readme and add the next chapter to your engine documentation. This way QuakeC coders will know how to use the Extension System and EBFS with your engine. ## Part III - How to use the Extension System in QuakeCThe Extension System is implemented by using the Enhanced BuiltIn Function System (EBFS) of the Quake Standards Group (QSG). Check out their homepage for more details on EBFS and other additional builtin functions. Using the Extension System and EBFS in your addons is very easy. You only have to make changes to DEFS.QC and WORLD.QC plus all occurences where you use extensions or additional builtin functions. Here is an example for a fictional extension called "tutorial_sample".
You add the declaration of the new builtin function "extension_find" just like normal plus a corresponding variable for it. For each extension you should also add a corresponding variable. The declaration of the EBFS function "builtin_find" is obligatory. // 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes start float(string s) builtin_find = #100; float qc_builtin_find; // 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes end // 2001-10-20 Extension System by LordHavoc start float(string s) extension_find = #99; float qc_extension_find; // 2001-10-20 Extension System by LordHavoc end float ext_tut_sample; // 2001-10-20 New Extension: tutorial_sample by Maddes
At the beginning of the function "worldspawn" (this is when a map is loaded) you have to check for the needed additional functions and extensions. You check for additional builtin functions with the EBFS function "builtin_find". It returns the function number of a given function name. The result is zero when the function does not exist. If the function exists, the QuakeC code has to check if the returned function number is the same as defined in DEFS.QC, this is for avoiding problems with non-compliant engines. If a really necessary function is not available, you should dump the game with the regular builtin function "error", pointing the user to the QSG homepage for a compliant engine. As the EBFS function "builtin_find" is an additional builtin function too, the above system doesn't work for it. Hence there is also a new cvar called "pr_builtin_find" which contains the function number of "builtin_find". After checking all the builtin functions you can check for the extensions. // 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes start qc_builtin_find = cvar("pr_builtin_find"); if (qc_builtin_find) { if (qc_builtin_find != 100) { dprint("Builtin function \"builtin_find\" is #"); dprint(ftos(qc_builtin_find)); dprint(" and not #100 - IGNORED!!!\n"); qc_builtin_find = 0; } } // check for additional builtin functions qc_extension_find = 0; // 2001-10-20 Extension System by LordHavoc if (qc_builtin_find) { // 2001-10-20 Extension System by LordHavoc start qc_extension_find = builtin_find("extension_find"); if (qc_extension_find) { if (qc_extension_find != 99) { dprint("Builtin function \"extension_find\" is #"); dprint(ftos(qc_extension_find)); dprint(" and not #99 - IGNORED!!!\n"); qc_extension_find = 0; } } // 2001-10-20 Extension System by LordHavoc end } // 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes end // 2001-10-20 Extension System by LordHavoc start if (qc_extension_find) { ext_tut_sample = extension_find("tutorial_sample"); // 2001-10-20 New Extension: tutorial_sample by Maddes } // 2001-10-20 Extension System by LordHavoc end
Everytime you want to use one of the extensions you have determine if it is available by checking its corresponding variable. if (ext_tut_sample) { // Do here what is necessary for this extension } Instead of using a separate variable for each extension, you could also use variables with bit flags (Note: 24 bit flags are the maximum of a float). |