Tutorial *138*

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 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 their code as engine coders (means following the QSG standards), otherwise they will never notice there 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.

Some may point out that LordHavoc 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 ; inumfunctions; 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 ; inumfunctions; 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\n

					Use 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.


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.



 
Not logged in
Sign up
Login:
Passwd: