================================================================
Title         : Tutorial: Implementing the Enhanced BuiltIn Function System into the Quake engine
                (including new builtin functions: cmd_find, cvar_find and cvar_string)
Date          : 2001-10-05
Filename      : EBFS.TXT
Author        : Matthias "Maddes" Buecher
Email Address : maddes@go.to
Homepage      : Quake Info Pool
                http://www.quake-info-pool.net/
                Quake Standards Group (short QSG)
                http://www.quakesrc.org/
Complexity    : Moderate
================================================================



Index
=====
Part I   - Purpose and system explanation
Part II  - Implementing EBFS into the Quake engine
Part III - Rules for registering new builtin functions to the QSG list
Part IV  - Four examples of implementing new builtin functions with EBFS
           (builtin functions: cmd_find, cvar_find, cvar_string, WriteFloat)
Part V   - Activating some useful builtin functions of the preliminary Quake 2 code
           (builtin functions: sin, cos, sqrt, etos)



Part I - Purpose and system explanation
=======================================
All QuakeC coders want to have new builtin functions to use in their addons.
The problem is to keep a standard among all the different engine ports.
That is where the Quake Standards Group came in. The QSG keeps a list of all
additional builtin functions, so that every engine uses the same function
numbers.

But how can the QuakeC coder determine during run-time if the engine supports
all the needed new builtin functions?
That's what the Enhanced BuiltIn Function System (short EBFS) is for.

With EBFS the new builtin function "builtin_find" is added to the engine. It
returns the function number of a given function name. The result is zero when
the function does not exist.

As the EBFS function "builtin_find" is a new builtin function too, the above
system doesn't work for it. Hence there is also a new cvar called
"pr_builtin_find" needed which contains the function number of "builtin_find".

Some days after I send the first version of this tutorial to the QSG for
approval, Sander "FireStorm" van Rossen <mv_firestorm@hotmail.com> did a posting
on the QSG forum about remapping builtin functions to the data stored in the
PROGS.DAT.
Testing showed that there can be problems with QuakeC addons which replace
builtin functions with QuakeC functions and redefines the builtin functions
under a different name as the engine can not assign this unknown function. A
good workaround is to assign all unassigned functions to their default number if
possible.
So this "BuiltIn Function Remapping" is an option which is controlled by the
cvar "pr_builtin_remap". It defaults to zero ("off") as QuakeC coders should be
as thoroughly with there code as engine coders (means following the QSG
standards), otherwise they will never notice their mistakes.
When a host error occurs the user is pointed to the "BuiltIn Function
Remapping", so they may be able to play the QuakeC addon.

With EBFS the old builtin array is no more static, it is created from a new
array on the fly when a PROGS.DAT is loaded. The run-time execution of the
PROGS.DAT stays the same, so there are no performance penalties because of the
dynamic way of using the builtin numbers.

You can download this tutorial including "How_to_use_EBFS_in_QuakeC.txt" here. (Bram: place download link here)

Some may point out that Lord Havoc already introduced a similar system to check
for new engine extensions.
The EBFS does not replace his system as it is still useful for various other
engine extensions, but the EBFS simplifies adding new builtin functions to the
engine (no more padding with PF_FixMe), allows to check the actual function
numbers for QuakeC coders and provides a remapping functionality for the user.



Part II  - Implementing EBFS into the Quake engine
==================================================
Adding EBFS to your engine is very easy, even if you did lots of changes to your
own engine.

The new builtin function "builtin_find" and the dynamic number assignment need
additional data, so we need to define a new structure for it.
Go into PROGS.H and after...

typedef void (*builtin_t) (void);
extern builtin_t	*pr_builtins;
extern int			pr_numbuiltins;

... add ...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
typedef struct ebfs_builtin_s
{
	int			default_funcno;
	char		*funcname;
	builtin_t	function;
	int			funcno;
} ebfs_builtin_t;

extern ebfs_builtin_t	pr_ebfs_builtins[];
extern int				pr_ebfs_numbuiltins;

#define PR_DEFAULT_FUNCNO_BUILTIN_FIND	100

extern cvar_t	pr_builtin_find;
extern cvar_t	pr_builtin_remap;

#define PR_DEFAULT_FUNCNO_EXTENSION_FIND	99	// 2001-10-20 Extension System by Lord Havoc/Maddes
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

This added the default function number, its name and the assigned number to the
data. The assigned number will be used to build the run-time builtin array for
QuakeC execution, and it is also needed for the return value of "builtin_find".
The name will be used to find a function when searched with "builtin_find" or
when remapping the functions from the PROGS.DAT data.


Now you have to add the EBFS data to PR_CMDS.C. But first completely disable the
pr_builtin[] array at the end of PR_CMDS.C and also remove the assignment of the
two builtin variables. The result looks like this...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
/*
builtin_t pr_builtin[] =
{
PF_Fixme,
PF_makevectors,	// void(entity e)	makevectors 		= #1;
.
.
.
PF_setspawnparms
};
*/
builtin_t *pr_builtins;
int pr_numbuiltins;
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

... after this add the new EBFS data...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
// for builtin function definitions see Quake Standards Group at http://www.quakesrc.org/
ebfs_builtin_t pr_ebfs_builtins[] =
{
	{   0, NULL, PF_Fixme },				// has to be first entry as it is needed for initialization in PR_LoadProgs()
	{   1, "makevectors", PF_makevectors },	// void(entity e)	makevectors 		= #1;
	{   2, "setorigin", PF_setorigin },		// void(entity e, vector o) setorigin	= #2;
	{   3, "setmodel", PF_setmodel },		// void(entity e, string m) setmodel	= #3;
	{   4, "setsize", PF_setsize },			// void(entity e, vector min, vector max) setsize = #4;
//	{   5, "fixme", PF_Fixme },				// void(entity e, vector min, vector max) setabssize = #5;
	{   6, "break", PF_break },				// void() break						= #6;
	{   7, "random", PF_random },			// float() random						= #7;
	{   8, "sound", PF_sound },				// void(entity e, float chan, string samp) sound = #8;
	{   9, "normalize", PF_normalize },		// vector(vector v) normalize			= #9;
	{  10, "error", PF_error },				// void(string e) error				= #10;
	{  11, "objerror", PF_objerror },		// void(string e) objerror				= #11;
	{  12, "vlen", PF_vlen },				// float(vector v) vlen				= #12;
	{  13, "vectoyaw", PF_vectoyaw },		// float(vector v) vectoyaw		= #13;
	{  14, "spawn", PF_Spawn },				// entity() spawn						= #14;
	{  15, "remove", PF_Remove },			// void(entity e) remove				= #15;
	{  16, "traceline", PF_traceline },		// float(vector v1, vector v2, float tryents) traceline = #16;
	{  17, "checkclient", PF_checkclient },	// entity() clientlist					= #17;
	{  18, "find", PF_Find },				// entity(entity start, .string fld, string match) find = #18;
	{  19, "precache_sound", PF_precache_sound },	// void(string s) precache_sound		= #19;
	{  20, "precache_model", PF_precache_model },	// void(string s) precache_model		= #20;
	{  21, "stuffcmd", PF_stuffcmd },		// void(entity client, string s)stuffcmd = #21;
	{  22, "findradius", PF_findradius },	// entity(vector org, float rad) findradius = #22;
	{  23, "bprint", PF_bprint },			// void(string s) bprint				= #23;
	{  24, "sprint", PF_sprint },			// void(entity client, string s) sprint = #24;
	{  25, "dprint", PF_dprint },			// void(string s) dprint				= #25;
	{  26, "ftos", PF_ftos },				// void(string s) ftos				= #26;
	{  27, "vtos", PF_vtos },				// void(string s) vtos				= #27;
	{  28, "coredump", PF_coredump },
	{  29, "traceon", PF_traceon },
	{  30, "traceoff", PF_traceoff },
	{  31, "eprint", PF_eprint },			// void(entity e) debug print an entire entity
	{  32, "walkmove", PF_walkmove },		// float(float yaw, float dist) walkmove
//	{  33, "fixme", PF_Fixme },				// float(float yaw, float dist) walkmove
	{  34, "droptofloor", PF_droptofloor },
	{  35, "lightstyle", PF_lightstyle },
	{  36, "rint", PF_rint },
	{  37, "floor", PF_floor },
	{  38, "ceil", PF_ceil },
//	{  39, "fixme", PF_Fixme },
	{  40, "checkbottom", PF_checkbottom },
	{  41, "pointcontents", PF_pointcontents },
//	{  42, "fixme", PF_Fixme },
	{  43, "fabs", PF_fabs },
	{  44, "aim", PF_aim },
	{  45, "cvar", PF_cvar },
	{  46, "localcmd", PF_localcmd },
	{  47, "nextent", PF_nextent },
	{  48, "particle", PF_particle },
	{  49, "ChangeYaw", PF_changeyaw },
//	{  50, "fixme", PF_Fixme },
	{  51, "vectoangles", PF_vectoangles },

	{  52, "WriteByte", PF_WriteByte },
	{  53, "WriteChar", PF_WriteChar },
	{  54, "WriteShort", PF_WriteShort },
	{  55, "WriteLong", PF_WriteLong },
	{  56, "WriteCoord", PF_WriteCoord },
	{  57, "WriteAngle", PF_WriteAngle },
	{  58, "WriteString", PF_WriteString },
	{  59, "WriteEntity", PF_WriteEntity },

#ifdef QUAKE2
	{  60, "sin", PF_sin },
	{  61, "cos", PF_cos },
	{  62, "sqrt", PF_sqrt },
	{  63, "changepitch", PF_changepitch },
	{  64, "TraceToss", PF_TraceToss },
	{  65, "etos", PF_etos },
	{  66, "WaterMove", PF_WaterMove },
#endif

	{  67, "movetogoal", SV_MoveToGoal },
	{  68, "precache_file", PF_precache_file },
	{  69, "makestatic", PF_makestatic },

	{  70, "changelevel", PF_changelevel },
//	{  71, "fixme", PF_Fixme },

	{  72, "cvar_set", PF_cvar_set },
	{  73, "centerprint", PF_centerprint },

	{  74, "ambientsound", PF_ambientsound },

	{  75, "precache_model2", PF_precache_model },
	{  76, "precache_sound2", PF_precache_sound },	// precache_sound2 is different only for qcc
	{  77, "precache_file2", PF_precache_file },

	{  78, "setspawnparms", PF_setspawnparms },

//	{  81, "stof", PF_stof },	// 2001-09-20 QuakeC string manipulation by FrikaC/Maddes

// 2001-11-15 DarkPlaces general builtin functions by Lord Havoc  start
// not implemented yet
/*
	{  90, "tracebox", PF_tracebox },
	{  91, "randomvec", PF_randomvec },
	{  92, "getlight", PF_GetLight },	// not implemented yet
	{  93, "cvar_create", PF_cvar_create },		// 2001-09-18 New BuiltIn Function: cvar_create() by Maddes
	{  94, "fmin", PF_fmin },
	{  95, "fmax", PF_fmax },
	{  96, "fbound", PF_fbound },
	{  97, "fpow", PF_fpow },
	{  98, "findfloat", PF_FindFloat },
	{ PR_DEFAULT_FUNCNO_EXTENSION_FIND, "extension_find", PF_extension_find },	// 2001-10-20 Extension System by Lord Havoc/Maddes
	{   0, "registercvar", PF_cvar_create },		// 0 indicates that this entry is just for remapping (because of name change)
	{   0, "checkextension", PF_extension_find },
*/
// 2001-11-15 DarkPlaces general builtin functions by Lord Havoc  end

	{ PR_DEFAULT_FUNCNO_BUILTIN_FIND, "builtin_find", PF_builtin_find },		// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes

// not implemented yet
/*
	{ 101, "cmd_find", PF_cmd_find },		// 2001-09-16 New BuiltIn Function: cmd_find() by Maddes

	{ 102, "cvar_find", PF_cvar_find },		// 2001-09-16 New BuiltIn Function: cvar_find() by Maddes

	{ 103, "cvar_string", PF_cvar_string },	// 2001-09-16 New BuiltIn Function: cvar_string() by Maddes

	{ 105, "cvar_free", PF_cvar_free },		// 2001-09-18 New BuiltIn Function: cvar_free() by Maddes

	{ 106, "NVS_InitSVCMsg", PF_NVS_InitSVCMsg },	// 2000-05-02 NVS SVC by Maddes

	{ 107, "WriteFloat", PF_WriteFloat },	// 2001-09-16 New BuiltIn Function: WriteFloat() by Maddes

	{ 108, "etof", PF_etof },	// 2001-09-25 New BuiltIn Function: etof() by Maddes

	{ 109, "ftoe", PF_ftoe },	// 2001-09-25 New BuiltIn Function: ftoe() by Maddes
*/

// 2001-09-20 QuakeC file access by FrikaC/Maddes  start
// not implemented yet
/*
	{ 110, "fopen", PF_fopen },
	{ 111, "fclose", PF_fclose },
	{ 112, "fgets", PF_fgets },
	{ 113, "fputs", PF_fputs },
	{   0, "open", PF_fopen },		// 0 indicates that this entry is just for remapping (because of name and number change)
	{   0, "close", PF_fclose },
	{   0, "read", PF_fgets },
	{   0, "write", PF_fputs },
*/
// 2001-09-20 QuakeC file access by FrikaC/Maddes  end

// 2001-09-20 QuakeC string manipulation by FrikaC/Maddes  start
// not implemented yet
/*
	{ 114, "strlen", PF_strlen },
	{ 115, "strcat", PF_strcat },
	{ 116, "substring", PF_substring },
	{ 117, "stov", PF_stov },
	{ 118, "strzone", PF_strzone },
	{ 119, "strunzone", PF_strunzone },
	{   0, "zone", PF_strzone },		// 0 indicates that this entry is just for remapping (because of name and number change)
	{   0, "unzone", PF_strunzone },
*/
// 2001-09-20 QuakeC string manipulation by FrikaC/Maddes  end

// 2001-11-15 DarkPlaces general builtin functions by Lord Havoc  start
// not implemented yet
/*
	{ 400, "copyentity", PF_... },
	{ 401, "setcolor", PF_... },
	{ 402, "findchain", PF_... },
	{ 403, "findchainfloat", PF_... },
	{ 404, "effect", PF_... },
	{ 405, "te_blood", PF_... },
	{ 406, "te_bloodshower", PF_... },
	{ 407, "te_explosionrgb", PF_... },
	{ 408, "te_particlecube", PF_... },
	{ 409, "te_particlerain", PF_... },
	{ 410, "te_particlesnow", PF_... },
	{ 411, "te_spark", PF_... },
	{ 412, "te_gunshotquad", PF_... },
	{ 413, "te_spikequad", PF_... },
	{ 414, "te_superspikequad", PF_... },
	{ 415, "te_explosionquad", PF_... },
	{ 416, "te_smallflash", PF_... },
	{ 417, "te_customflash", PF_... },
	{ 418, "te_gunshot", PF_... },
	{ 419, "te_spike", PF_... },
	{ 420, "te_superspike", PF_... },
	{ 421, "te_explosion", PF_... },
	{ 422, "te_tarexplosion", PF_... },
	{ 423, "te_wizspike", PF_... },
	{ 424, "te_knightspike", PF_... },
	{ 425, "te_lavasplash", PF_... },
	{ 426, "te_teleport", PF_... },
	{ 427, "te_explosion2", PF_... },
	{ 428, "te_lightning1", PF_... },
	{ 429, "te_lightning2", PF_... },
	{ 430, "te_lightning3", PF_... },
	{ 431, "te_beam", PF_... },
	{ 432, "vectorvectors", PF_... },
*/
// 2001-11-15 DarkPlaces general builtin functions by Lord Havoc  end
};

int pr_ebfs_numbuiltins = sizeof(pr_ebfs_builtins)/sizeof(pr_ebfs_builtins[0]);
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

I already added all registered functions (outcommented) including FrikaC's
file access QSG tutorial, Dark Places 1.05 functions and more.
The function "builtin_find", which we will implement later, is already active in
the list.
You also see that you can have different names for the same builtin function for
remapping reasons. Note that the right name must have a default function
number and all extra names must use the default function number zero (0).


As we disabled/removed the old pr_builtin array, we have to rebuild it
dynamically when a PROGS.DAT is loaded. This will be done by allocating enough
memory for it and putting the function pointer of all assigned functions into
the slot of their actual function number.
But before we can do this we have to check what numbers the functions are
assigned to, if no remapping has to be done then the default numbers are used.
When remapping is requested, then first the engine has to search all defined
builtin functions of the PROGS.DAT in the EBFS data array by name and if found,
assign the number used in the PROGS.DAT. Then all unassigned functions are tried
to assign their default number, if not already occupied by a remapped function.

All this has to be done in PR_EDICT.C where the PROGS.DAT is loaded, so we add
the new cvars at the top of it first...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
cvar_t	pr_builtin_find = {"pr_builtin_find", "0", false, false};
cvar_t	pr_builtin_remap = {"pr_builtin_remap", "0", false, false};
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

... and add some more variables to the top of the PR_LoadProgs function ...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  start
	int 	j;
	int		funcno;
	char	*funcname;
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  end

... search for the following code in PR_LoadProgs ...

	for (i=0 ; i<progs->numfunctions; i++)
	{
	pr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement);
	pr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start);
	pr_functions[i].s_name = LittleLong (pr_functions[i].s_name);
	pr_functions[i].s_file = LittleLong (pr_functions[i].s_file);
	pr_functions[i].numparms = LittleLong (pr_functions[i].numparms);
	pr_functions[i].locals = LittleLong (pr_functions[i].locals);
	}

... and replace it with this code ...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  start
	// initialize function numbers for PROGS.DAT
	pr_numbuiltins = 0;
	pr_builtins = NULL;
	if (pr_builtin_remap.value)
	{
		// remove all previous assigned function numbers
		for ( j=1 ; j < pr_ebfs_numbuiltins; j++)
		{
			pr_ebfs_builtins[j].funcno = 0;
		}
	}
	else
	{
		// use default function numbers
		for ( j=1 ; j < pr_ebfs_numbuiltins; j++)
		{
			pr_ebfs_builtins[j].funcno = pr_ebfs_builtins[j].default_funcno;
			// determine highest builtin number (when NOT remapped)
			if (pr_ebfs_builtins[j].funcno > pr_numbuiltins)
			{
				pr_numbuiltins = pr_ebfs_builtins[j].funcno;
			}
		}
	}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  end

	for (i=0 ; i<progs->numfunctions; i++)
	{
	pr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement);
	pr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start);
	pr_functions[i].s_name = LittleLong (pr_functions[i].s_name);
	pr_functions[i].s_file = LittleLong (pr_functions[i].s_file);
	pr_functions[i].numparms = LittleLong (pr_functions[i].numparms);
	pr_functions[i].locals = LittleLong (pr_functions[i].locals);
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  start
		if (pr_builtin_remap.value)
		{
			if (pr_functions[i].first_statement < 0)	// builtin function
			{
				funcno = -pr_functions[i].first_statement;
				funcname = pr_strings + pr_functions[i].s_name;

				// search function name
				for ( j=1 ; j < pr_ebfs_numbuiltins ; j++)
				{
					if (!(Q_strcasecmp(funcname, pr_ebfs_builtins[j].funcname)))
					{
						break;	// found
					}
				}

				if (j < pr_ebfs_numbuiltins)	// found
				{
					pr_ebfs_builtins[j].funcno = funcno;
				}
				else
				{
					Con_DPrintf("Can not assign builtin number #%i to %s - function unknown\n", funcno, funcname);
				}
			}
		}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  end
	}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  start
	if (pr_builtin_remap.value)
	{
		// check for unassigned functions and try to assign their default function number
		for ( i=1 ; i < pr_ebfs_numbuiltins; i++)
		{
			if ((!pr_ebfs_builtins[i].funcno) && (pr_ebfs_builtins[i].default_funcno))	// unassigned and has a default number
			{
				// check if default number is already assigned to another function
				for ( j=1 ; j < pr_ebfs_numbuiltins; j++)
				{
					if (pr_ebfs_builtins[j].funcno == pr_ebfs_builtins[i].default_funcno)
					{
						break;	// number already assigned to another builtin function
					}
				}

				if (j < pr_ebfs_numbuiltins)	// already assigned
				{
					Con_DPrintf("Can not assign default builtin number #%i to %s - number is already assigned to %s\n", pr_ebfs_builtins[i].default_funcno, pr_ebfs_builtins[i].funcname, pr_ebfs_builtins[j].funcname);
				}
				else
				{
					pr_ebfs_builtins[i].funcno = pr_ebfs_builtins[i].default_funcno;
				}
			}
			// determine highest builtin number (when remapped)
			if (pr_ebfs_builtins[i].funcno > pr_numbuiltins)
			{
				pr_numbuiltins = pr_ebfs_builtins[i].funcno;
			}
		}
	}
	pr_numbuiltins++;

	// allocate and initialize builtin list for execution time
	pr_builtins = Hunk_AllocName (pr_numbuiltins*sizeof(builtin_t), "builtins");
	for ( i=0 ; i < pr_numbuiltins ; i++)
	{
		pr_builtins[i] = pr_ebfs_builtins[0].function;
	}

	// 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 Lord Havoc/Maddes (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 Lord Havoc/Maddes (DP compatibility)  start
// not implemented yet
/*
		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 Lord Havoc/Maddes (DP compatibility)  end
	}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes/Firestorm  end

... in PR_Init add the following lines ...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
	Cvar_RegisterVariable (&pr_builtin_find);
	Cvar_RegisterVariable (&pr_builtin_remap);
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end


You also have to implement the new builtin function "builtin_find".
Back in PR_CMDS.C add the following code anywhere before the builtin variables...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
/*
=================
PF_builtin_find

float builtin_find (string)
=================
*/
void PF_builtin_find (void)
{
	int		j;
	float	funcno;
	char	*funcname;

	funcno = 0;
	funcname = G_STRING(OFS_PARM0);

	// search function name
	for ( j=1 ; j < pr_ebfs_numbuiltins ; j++)
	{
		if ((pr_ebfs_builtins[j].funcname) && (!(Q_strcasecmp(funcname,pr_ebfs_builtins[j].funcname))))
		{
			break;	// found
		}
	}

	if (j < pr_ebfs_numbuiltins)
	{
		funcno = pr_ebfs_builtins[j].funcno;
	}

	G_FLOAT(OFS_RETURN) = funcno;
}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

The function searches for the given function name, and if it finds the function
it returns the corresponding function number.


As some function numbers and names have changed when cleaning up the known
functions for the QSG registry, it is useful to point out the remapping
functionality to the user, when a builtin function wasn't found during execution
time.
Go into PR_EXEC.C to PR_ExecuteProgram and add the following variables to it...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
	char	*funcname;
	char	*remaphint;
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

... and change ...

		if (newf->first_statement < 0)
		{	// negative statements are built in functions
			i = -newf->first_statement;
			if (i >= pr_numbuiltins)
				PR_RunError ("Bad builtin call number");
			pr_builtins[i] ();
			break;
		}

... into ...

		if (newf->first_statement < 0)
		{	// negative statements are built in functions
			i = -newf->first_statement;
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
			if ( (i >= pr_numbuiltins)
			||   (pr_builtins[i] == pr_ebfs_builtins[0].function) )
			{
				funcname = pr_strings + newf->s_name;
				if (pr_builtin_remap.value)
				{
					remaphint = NULL;
				}
				else
				{
					remaphint = "Try \"builtin remapping\" by setting PR_BUILTIN_REMAP to 1\n";
				}
				PR_RunError ("Bad builtin call number %i for %s\nPlease contact the PROGS.DAT author\nUse BUILTINLIST to see all assigned builtin functions\n%s", i, funcname, remaphint);
			}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end
			pr_builtins[i] ();
			break;
		}


A nice addition is the new command "builtinlist", which lists all builtin functions
with their numbers and names.
In PR_EDICT.C before PR_Init() add the following function...

// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  start
/*
=============
PR_BuiltInList_f

For debugging, prints all builtin functions with assigned and default number
=============
*/
void PR_BuiltInList_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=1; i < pr_ebfs_numbuiltins; i++)
	{
		if (partial && Q_strncasecmp (partial, pr_ebfs_builtins[i].funcname, len))
		{
			continue;
		}
		count++;
		Con_Printf ("%i(%i): %s\n", pr_ebfs_builtins[i].funcno, pr_ebfs_builtins[i].default_funcno, pr_ebfs_builtins[i].funcname);
	}

	Con_Printf ("------------\n");
	if (partial)
	{
		Con_Printf ("%i beginning with \"%s\" out of ", count, partial);
	}
	Con_Printf ("%i builtin functions\n", i);
}
// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes  end

Then add this function as a command in PR_Init() with the following line...

	Cmd_AddCommand ("builtinlist", PR_BuiltInList_f);	// 2001-09-14 Enhanced BuiltIn Function System (EBFS) by Maddes

Now recompile the engine.


Last but not least, do not forget to document the EBFS in your readme and add
the file "How_to_use_EBFS_in_QuakeC.txt" to your engine documentation. This way
QuakeC coders will know how to use EBFS with your engine.
Download the textfile here. (Bram: place download link from above here again)



Part III - Rules for registering new builtin functions to the QSG list
======================================================================
If you want to implement a new builtin function it is highly recommended to
check out the existing builtin functions on the QSG homepage before. If the
functionality you are going to create is not listed, then start coding and give
it a dummy function number of 9999 (and counting downwards for others).

If you are done and have tested thoroughly, create a short tutorial for this
function (similar to part IV) and register your function on the QSG homepage.
Please include your name, email address (for questions), a description and the
short tutorial. When your function is registered you will get it's real function
number.



Part IV  - Four examples of implementing new builtin functions with EBFS
========================================================================
a) The function is called "cmd_find" and it checks if an engine command is
   available. The return value is 1 if the command exists, otherwise it is 0.

   In PR_CMDS.C add the following code anywhere before the builtin variables...

// 2001-09-16 New BuiltIn Function: cmd_find() by Maddes  start
/*
=================
PF_cmd_find

float cmd_find (string)
=================
*/
void PF_cmd_find (void)
{
	char	*cmdname;
	float	result;

	cmdname = G_STRING(OFS_PARM0);

	result = Cmd_Exists (cmdname);

	G_FLOAT(OFS_RETURN) = result;
}
// 2001-09-16 New BuiltIn Function: cmd_find() by Maddes  end

   ... and to the EBFS data array add ...

	{ 101, "cmd_find", PF_cmd_find },		// 2001-09-16 New BuiltIn Function: cmd_find() by Maddes

   Done.


b) The function is called "cvar_find" and checks if a console variable is
   available. The return value is 1 if the cvar exists, otherwise it is 0.

   In PR_CMDS.C add the following code anywhere before the builtin variables...

// 2001-09-16 New BuiltIn Function: cvar_find() by Maddes  start
/*
=================
PF_cvar_find

float cvar_find (string)
=================
*/
void PF_cvar_find (void)
{
	char	*varname;
	float	result;

	varname = G_STRING(OFS_PARM0);

	result = 0;
	if (Cvar_FindVar (varname))
	{
		result = 1;
	}

	G_FLOAT(OFS_RETURN) = result;
}
// 2001-09-16 New BuiltIn Function: cvar_find() by Maddes  end

   ... and to the EBFS data array add ...

	{ 102, "cvar_find", PF_cvar_find },		// 2001-09-16 New BuiltIn Function: cvar_find() by Maddes

   Done.


c) The function is called "cvar_string" and returns the string of a console
   variable.

   In PR_CMDS.C add the following code anywhere before the builtin variables...

// 2001-09-16 New BuiltIn Function: cvar_string() by Maddes  start
/*
=================
PF_cvar_string

string cvar_string (string)
=================
*/
void PF_cvar_string (void)
{
	char	*varname;
	cvar_t	*var;

	varname = G_STRING(OFS_PARM0);
	var = Cvar_FindVar (varname);
	if (!var)
	{
		Con_DPrintf ("Cvar_String: variable \"%s\" not found\n", varname);	// 2001-09-09 Made 'Cvar not found' a developer message by Maddes
		G_INT(OFS_RETURN) = OFS_NULL;
	}
	else
	{
		G_INT(OFS_RETURN) = var->string - pr_strings;
	}
}
// 2001-09-16 New BuiltIn Function: cvar_string() by Maddes  end

   ... and to the EBFS data array add ...

	{ 103, "cvar_string", PF_cvar_string },		// 2001-09-16 New BuiltIn Function: cvar_string() by Maddes

   Done.


d) The function is called "WriteFloat" and is the missing Write function.

   In PR_CMDS.C add the following code anywhere before the builtin variables...

// 2001-09-16 New BuiltIn Function: WriteFloat() by Maddes  start
/*
PF_WriteFloat

void (float to, float f) WriteFloat
*/
void PF_WriteFloat (void)
{
	MSG_WriteFloat (WriteDest(), G_FLOAT(OFS_PARM1));
}
// 2001-09-16 New BuiltIn Function: WriteFloat() by Maddes  end

   ... and to the EBFS data array add ...

	{ 107, "WriteFloat", PF_WriteFloat },	// 2001-09-16 New BuiltIn Function: WriteFloat() by Maddes

   Done.


You see it it is very easy to add your own builtin functions to the system.
If you do have new builtin functions then check out the previous part how to
submit them to the QSG.



Part V   - Activating some useful builtin functions of the preliminary Quake 2 code
===================================================================================
There are some simple but useful builtin functions done for the preliminary
Quake 2 code: sin, cos, sqrt and etos.

First you have to activate the code for Classic Quake.

Remove the "#ifdef QUAKE2" before and the "#endif" behind PF_etos().

Before PF_sin() place the following line...

#endif	// 2001-09-16 Quake 2 builtin functions: sin, cos, sqrt, etos by id/Maddes

... and remove the "#endif" behind PF_sqrt().

At last replace the following in the EBFS data array...

#ifdef QUAKE2
	{  60, "sin", PF_sin },
	{  61, "cos", PF_cos },
	{  62, "sqrt", PF_sqrt },
	{  63, "changepitch", PF_changepitch },
	{  64, "TraceToss", PF_TraceToss },
	{  65, "etos", PF_etos },
	{  66, "WaterMove", PF_WaterMove },
#endif

    ... with ...

// 2001-09-16 Quake 2 builtin functions: sin, cos, sqrt, etos by id/Maddes  start
	{  60, "sin", PF_sin },
	{  61, "cos", PF_cos },
	{  62, "sqrt", PF_sqrt },
#ifdef QUAKE2
	{  63, "changepitch", PF_changepitch },
	{  64, "TraceToss", PF_TraceToss },
#endif
	{  65, "etos", PF_etos },
#ifdef QUAKE2
	{  66, "WaterMove", PF_WaterMove },
#endif
// 2001-09-16 Quake 2 builtin functions: sin, cos, sqrt, etos by id/Maddes  end

Done.
