Mailto: maddes@bigfoot.com
Index
Part 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
Many 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".
Adding 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 QuakeC
The 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".
Step #1 - DEFS.QC:
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
|
|
Step #2 - WORLD.QC:
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
|
|
Step #3 - Using extensions anywhere
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).
|