Quake Info Pool - Classic Quake - Engine Fixes

Back | Last changes: 2001-11-25 | Contact Maddes

# Quake Engine Fixes

## Index

- "Cache_Alloc: allready allocated"
- Coop not cleared when selecting SP through menu
- Removed unwanted spaces in FTOS() result and increased precision
- MOVETYPE_PUSH error with non bsp models
- SV_MAXVELOCITY fix
- Compatibilty for PROGS.DAT of original Quake version in new ports
- EndFrame function
- +USE fix

## Notes:

- Get my QIP Quake source from the download page.
- If you want to join an open source project, but don't know how to setup WinCVS then check out this text file.
- Please visit the Quake Standards Group and join the Quake Sourcecode Mailing List.
- Some fixes were left out by id Software, search for "FIX ME", "FIXME" and "TODO" in the source.

## "Cache_Alloc: allready allocated"

Here are some information from Zoid, which could be helpful for programming a new port:

    It is a bug in Quake in respect to sprite loading.

    Quake has at most, 256 (0-255) models in memory at one time.
    Models are bsp (and bsp sub-models, each *111 model is a
    separate model), sprite and alias models.

    Since there's only 256 models (and models are "cleared" between map
    loads, so loading is faster, this is one reason why Quake loads faster
    than Q2), there is a caching system for alias models.  The system
    throws models all models away (but alias models are "in the cache")
    when a map loads.

    The only models carried between maps are alias models and they are the
    only ones that go into the Caching system (pics also go into the Cache
    system, but that isn't the problem here).

    What happens, is sprites set mod->cache.data!  This is a bug!
    cache.data should only be set for alias models.  It explodes when a
    sprite model is thrown from model list (it's considered 'available'
    since all sprite models are thrown away on map load) and then an alias
    model is tried to load into it's place.

    The reason you don't see this very often is it's actually rare for a
    sprite model to get replaced by an alias model.  I have seen this
    error before, but only when there are a lot of models.  You have to
    hit the 256 model limit and then happen to free a sprite model for an
    alias one.  So the chances are less.  You will hardly EVER see this in
    a deathmatch game, since hitting the 256 model limit is almost
    impossible (since the same set of models getting loaded over and over
    in deathmatch games, so models hardly ever get tossed).

    The actual fix in the Quake code for this is to add this to
    Mod_ClearAll in model.c:

    void Mod_ClearAll (void)
    {
    	int  i;
    	model_t *mod;

    	for (i=0 , mod=mod_known; i<mod_numknown; i++, mod++)
    	{
    		mod->needload = NL_UNREFERENCED;
    	//ADD THIS LINE HERE:
    		if (mod->type == mod_sprite) mod->cache.data = NULL;
    	}
    }

    (The mod->cache.data memory is actually free'd earlier by the Heap
    clearing functions).

Comment by Maddes:
This looks more like a workaround, the real problem should reside within the sprite loading routines.
Just my two cents.

## Coop not cleared when selecting SP through menu

(with help from Robert "Frog" Field)
Unfortunately "coop" is not recognized when "maxplayers" is set through the menu functions. Setting the console variables like it is done in MaxPlayers_f() of net_main.c is wrong. If you set "coop" to 1 first and then "maxplayers" to >1 the next map would be played in coop and deathmatch at the same time :) Fortunately "coop" supersedes "deathmatch", so everything works out, but if you look at "deathmatch" it's 0.000000 instead of 0 (weird!, WinQuake 1.09 still behaves this way).
Solution:

selecting SP in menu | "maxplayers" = 1 (unchanged)
changing "maxplayers" | if (n = 1), "coop" = 0, "deathmatch" = 0 if (n > 1 && "coop" == 0), "deathmatch" = 1

    "net_main.c"

    static void MaxPlayers_f (void)
    {
    	...

    	svs.maxclients = n;
    	if (n == 1)
    	{					// 1999-07-30 coop and deathmatch flag fix by Frog/Maddes
    		Cvar_Set ("deathmatch", "0");
    // 1999-07-30 coop and deathmatch flag fix by Frog/Maddes  start
    		Cvar_Set ("coop", "0");
    	}
    // 1999-07-30 coop and deathmatch flag fix by Frog/Maddes  end
    	else
    // 1999-07-30 coop and deathmatch flag fix by Frog/Maddes  start
    	{
    		if (coop.value)
    			Cvar_Set ("deathmatch", "0");
    		else
    // 1999-07-30 coop and deathmatch flag fix by Frog/Maddes  end
    			Cvar_Set ("deathmatch", "1");
    	}					// 1999-07-30 coop and deathmatch flag fix by Frog/Maddes
    }

And then you have to avoid that somebody creates this ugly setting by hand.
Solution:

changing "deathmatch" | if (set), "coop" = 0
changing "coop" | if (set), "deathmatch" = 0

    "cvar.c"

    void Cvar_Set (char *var_name, char *value)
    {
    	...

    // 1999-09-06 deathmatch/coop not at the same time fix by Maddes  start
    	if ( (var->value != 0) && (!Q_strcmp (var->name, deathmatch.name)) )
    		Cvar_Set ("coop", "0");

    	if ( (var->value != 0) && (!Q_strcmp (var->name, coop.name)) )
    		Cvar_Set ("deathmatch", "0");
    // 1999-09-06 deathmatch/coop not at the same time fix by Maddes  end
    }

And when you also want to avoid those weird "0.00000" settings then you have to use this:

    "cvar.c"

    void Cvar_SetValue (char *var_name, float value)
    {
    	char	val[32];

    // 1999-09-07 weird zeros fix by Maddes  start
    	if (value == (int)value)
    		sprintf (val, "%d", (int)value);
    	else
    // 1999-09-07 weird zeros fix by Maddes  end
    		sprintf (val, "%f", value);
    	Cvar_Set (var_name, val);
    }

## Removed unwanted spaces in FTOS() result and increased precision

(with help from Robert "Frog" Field)
Unfortunately "cvar_set" can not understand values with leading spaces, hence it then sets the variable to zero.
Also only the first digit after the decimal point is returned, so the result has not the common precision of 2 digits.
QuakeC coders should round/floor/ceil values before printing if necessary, otherwise they get weird looking displays (like in CTF: "1.193546 seconds left").

    "pr_cmds.c"

    void PF_ftos (void)
    {
    	float v;
    	v = G_FLOAT(OFS_PARM0);

    	if (v == (int)v)
    		sprintf (pr_string_temp, "%d",(int)v);
    	else
    // 1999-07-25 FTOS fix by Maddes  start
    //		sprintf (pr_string_temp, "%5.1f",v);
    		sprintf (pr_string_temp, "%1f",v);
    // 1999-07-25 FTOS fix by Maddes  end
    	G_INT(OFS_RETURN) = pr_string_temp - pr_strings;
    }

As the format strings are stored in the executable as is, it is possible to hack the executable.
Just search for "%5.1f" with a "%d" directly before it. I always found it behind the following string "Parm 0 is not a client".
Remember to make a backup of the executable first, before you modify it.
Here are the offsets where I found the format string:

Executable | Hex Offset
--- | ---
WinQuake 1.09 | 0x6d858
GLQuake 0.97 | 0x539a8

## MOVETYPE_PUSH error with non bsp models

(with help from Forest "Lord Havoc" Hale)
First the error message is wrong, it should be "SOLID_BSP with a non bsp model", because SV_HullForEntity() only checks SOLID_BSPs for bsp models. The movetype has nothing to do with the model check.

    "world.c"

    hull_t *SV_HullForEntity (edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset)
    {
    	...
    // decide which clipping hull to use, based on the size
    	if (ent->v.solid == SOLID_BSP)
    	{	// explicit hulls in the BSP model
    		if (ent->v.movetype != MOVETYPE_PUSH)
    			Sys_Error ("SOLID_BSP without MOVETYPE_PUSH");

    		model = sv.models[ (int)ent->v.modelindex ];

    		if (!model || model->type != mod_brush)
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  start
    //			Sys_Error ("MOVETYPE_PUSH with a non bsp model");
    			Sys_Error ("SOLID_BSP with a non bsp model");
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  end
    	...
    }

Now to the real problem, in SV_PushMove() every entity which is checked becomes SOLID_BSP no matter what it was before, that's why the SOLID_NOT misc_teleporttrain entity in the end level can become SOLID_BSP which leads to the error.
So it's best to save the current solid state and restore it after the check. This avoids the error, but the SOLID_NOT entity does not behave like expected: it gets blocked. The blocking routine should only be used on entities with a blockable solid state.

    "sv_phys.c"

    void SV_PushMove (edict_t *pusher, float movetime)
    {
    	float	solid_backup;	// 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes
    	...
    		VectorCopy (check->v.origin, entorig);
    		VectorCopy (check->v.origin, moved_from[num_moved]);
    		moved_edict[num_moved] = check;
    		num_moved++;

    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  start
    		solid_backup = pusher->v.solid;
    		if ( solid_backup == SOLID_BSP		// everything that blocks: bsp models = map brushes = doors, plats, etc.
    		  || solid_backup == SOLID_BBOX		// normally boxes
    		  || solid_backup == SOLID_SLIDEBOX )	// normally monsters
    		{
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  end
    			// try moving the contacted entity
    			pusher->v.solid = SOLID_NOT;
    			SV_PushEntity (check, move);
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  start
    //			pusher->v.solid = SOLID_BSP;
    			pusher->v.solid = solid_backup;
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  end

    			// if it is still inside the pusher, block
    			block = SV_TestEntityPosition (check);
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  start
    		}
    		else
    			block = NULL;
    // 1999-10-07 MOVETYPE_PUSH fix by Lord Havoc/Maddes  end

    		if (block)
    		{	// fail the move
    	...
    }

## SV_MAXVELOCITY fix

When a velocity is checked against SV_MAXVELOCITY the Quake engine checks the vector's components (x, y, z) instead of it's resulting length. The check should be done just like the SV_MAXSPEED check.

    "sv_phys.c"

    void SV_CheckVelocity (edict_t *ent)
    {
    	int		i;
    	float	wishspeed;	// 1999-10-18 SV_MAXVELOCITY fix by Maddes

    //
    // bound velocity
    //
    	for (i=0 ; i<3 ; i++)
    	{
    		if (IS_NAN(ent->v.velocity[i]))
    		{
    			Con_Printf ("Got a NaN velocity on %s\n", pr_strings + ent->v.classname);
    			ent->v.velocity[i] = 0;
    		}
    		if (IS_NAN(ent->v.origin[i]))
    		{
    			Con_Printf ("Got a NaN origin on %s\n", pr_strings + ent->v.classname);
    			ent->v.origin[i] = 0;
    		}
    // 1999-10-18 SV_MAXVELOCITY fix by Maddes  start
    /*
    		if (ent->v.velocity[i] > sv_maxvelocity.value)
    			ent->v.velocity[i] = sv_maxvelocity.value;
    		else if (ent->v.velocity[i] < -sv_maxvelocity.value)
    			ent->v.velocity[i] = -sv_maxvelocity.value;
    */
    // 1999-10-18 SV_MAXVELOCITY fix by Maddes  end
    	}

    // 1999-10-18 SV_MAXVELOCITY fix by Maddes  start
    	wishspeed = Length(ent->v.velocity);
    	if (wishspeed > sv_maxvelocity.value)
    	{
    		VectorScale (ent->v.velocity, sv_maxvelocity.value/wishspeed, ent->v.velocity);
    		wishspeed = sv_maxvelocity.value;
    	}
    // 1999-10-18 SV_MAXVELOCITY fix by Maddes  end
    }

## Compatibilty for PROGS.DAT of original Quake version in new ports

To avoid breaking existing PROGS.DAT for the original Quake, new ports should include a way to keep compatibility with them (through parameters or a CRC when they extend the globals).

    "defs.qc"

    ...
    float		qcversion = 107;			// 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes

    //================================================
    void		end_sys_globals;		// flag for structure dumping
    //================================================

Now compile the QuakeC code and copy the generated PROGDEFS.H to the engine sourcecode.
Note that with modifying the globals the header CRC has changed too.
The QuakeC versions will be defined by the Quake Standards Group.

    "quakedef.h"

    ...
    // 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes  start
    #define PROGHEADER101_CRC	5927
    #define SUPPORTEDQC		107
    extern qboolean		keep_compatibility;
    // 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes  end

    "pr_edict.c"

    qboolean	keep_compatibility;	// 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes

    void PR_LoadProgs (void)
    {
    	...
    	if (progs->version != PROG_VERSION)
    		Sys_Error ("progs.dat has wrong version number (%i should be %i)", progs->version, PROG_VERSION);
    // 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes  start
    //	if (progs->crc != PROGHEADER_CRC)
    	if (progs->crc == PROGHEADER101_CRC)
    	{
    		keep_compatibility = true;
    		Con_DPrintf ("Old progs.dat found, compatibility turned on.\n");
    	}
    	else if (progs->crc == PROGHEADER_CRC)
    //		|| (progs->crc == PROGHEADER107_CRC)
    	{
    		keep_compatibility = false;
    		Con_DPrintf ("Enhanced progs.dat found, CRC is %i.\n", progs->crc);
    	}
    	else
    // 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes  end
    		Sys_Error ("progs.dat system vars have been modified, progdefs.h is out of date");

    	pr_functions = (dfunction_t *)((byte *)progs + progs->ofs_functions);
    	...
    	pr_edict_size = progs->entityfields * 4 + sizeof (edict_t) - sizeof(entvars_t);

    // 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes  start
    	if (!keep_compatibility)
    	{
    		if (pr_global_struct->qcversion <= SUPPORTEDQC)
    		{
    			Con_DPrintf ("Enhanced progs.dat uses QuakeC version %f.\n", pr_global_struct->qcversion);
    		}
    		else
    		{
    			Sys_Error ("Enhanced progs.dat needs QuakeC version %f which is NOT supported.\n", pr_global_struct->qcversion));
    		}
    	}
    // 1999-10-28 Compatibilty for PROGS.DAT of original Quake version by Maddes  end
    	...
    }

## EndFrame function

(with help from Ryan "Frika C" Smith)
Fortunately there is already a function which can check if a function exists in the QuakeC code, so we just need to look for it after loading the PROGS.DAT in pr_edict.c. Where we implement searching for an endframe function and storing it first.

    "pr_edict.c"

    func_t	EndFrame;	// 2000-01-02 EndFrame function by Maddes/FrikaC
    ...
    void PR_LoadProgs (void)
    {
    	dfunction_t	*f;	// 2000-01-02 EndFrame function by Maddes/FrikaC
    	...
    // 2000-01-02 EndFrame function by Maddes  start
    	EndFrame = 0;

    	if ((f = ED_FindFunction ("EndFrame")) != NULL)
    		EndFrame = (func_t)(f - pr_functions);
    // 2000-01-02 EndFrame function by Maddes/FrikaC  end
    }

Now we just need to call this function at the end of a frame when it exists. We have to declare the function variable in a header, as this it is needed in sv_phys.c where the frame and its entites are processed.

    "progs.h"

    extern func_t	EndFrame;	// 2000-01-02 EndFrame function by Maddes/FrikaC

    "sv_phys.c"

    void SV_Physics (void)
    {
    	...
    // 2000-01-02 EndFrame function by Maddes/FrikaC  start
    	if (EndFrame)
    	{
    		// let the progs know that a new frame has ended
    		pr_global_struct->self = EDICT_TO_PROG(sv.edicts);
    		pr_global_struct->other = EDICT_TO_PROG(sv.edicts);
    		pr_global_struct->time = sv.time;
    		PR_ExecuteProgram (EndFrame);
    	}
    // 2000-01-02 EndFrame function by Maddes/FrikaC  end

    	if (pr_global_struct->force_retouch)
    		pr_global_struct->force_retouch--;

    	sv.time += host_frametime;
    }

Last but not least I give you an example how to use the new endframe function and keep compatibility with older engines.

    "world.qc"

    // 2000-01-02 EndFrame function by Maddes  start
    float	endframe;
    void() EndFrameCode;
    // 2000-01-02 EndFrame function by Maddes  end
    ...
    void() StartFrame =
    {
    // 2000-01-02 EndFrame function by Maddes  start
    	if (!endframe)
    	{
    		if (framecount)				// skip start of very first frame
    		{
    			if (framecount < 10)		// just to avoid endless printing
    			{
    				dprint("Startframe: ");
    				EndFrameCode();
    			}
    		}
    	}
    // 2000-01-02 EndFrame function by Maddes  end
    	...
    };
    ...
    // 2000-01-02 EndFrame function by Maddes  start
    void() EndFrame =
    {
    	endframe = 1;

    	if (framecount < 10)		// just to avoid endless printing
    	{
    		dprint("Endframe: ");
    		EndFrameCode();
    	}
    };

    void() EndFrameCode =
    {
    	dprint("Endframe code\n");
    };
    // 2000-01-02 EndFrame function by Maddes  end

Now compile the QuakeC code and check it out on a new and old engine.

## +USE fix

As many addons use .button1 for misc data this should only be done in enhanced modes, although it only affects client entities. So we add a "-nouse" parameter for those old PROGS.DAT.

    "cl_input.c"

    void CL_SendMove (usercmd_t *cmd)
    {
    	...
    //
    // send button bits
    //
    	bits = 0;

    	if ( in_attack.state & 3 )
    		bits |= 1;
    	in_attack.state &= ~2;

    	if (in_jump.state & 3)
    		bits |= 2;
    	in_jump.state &= ~2;

    // 1999-10-29 +USE fix by Maddes  start
    	if ( in_use.state & 3 )
    		bits |= 4;
    	in_use.state &= ~2;
    // 1999-10-29 +USE fix by Maddes  end
    	...
    }

    "common.c"

    ...
    qboolean		nouse = false;	// 1999-10-29 +USE fix by Maddes
    ...
    void COM_InitArgv (int argc, char **argv)
    {
    	...
    // 1999-10-29 +USE fix by Maddes  start
    	if (COM_CheckParm ("-nouse"))
    	{
    		nouse = true;
    	}
    // 1999-10-29 +USE fix by Maddes  end
    }

    "quakedef.h"

    ...
    extern qboolean		nouse;	// 1999-10-29 +USE fix by Maddes

    "sv_user.c"

    void SV_ReadClientMove (usercmd_t *move)
    {
    	...
    // read buttons
    	bits = MSG_ReadByte ();
    	host_client->edict->v.button0 = bits & 1;
    // 1999-10-29 +USE fix by Maddes  start
    	if (!nouse)
    	{
    		host_client->edict->v.button1 = (bits & 4)>>2;
    	}
    // 1999-10-29 +USE fix by Maddes  end
    	...
    }

a Quake Info Pool page
© 1997-2022 by Maddes