Quake Info Pool - Classic Quake - QuakeC Workarounds

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

# QuakeC Workarounds

## QuakeC categories: Infos & Hints / Fixes / Workarounds / Enhancements

(discontinued, check out URQP source for more information)

## Index

- "ftos()" workaround
- "cvar_set()" workaround
- Incorrect "trigger_teleport" workaround
- "T_RadiusDamage()" workaround 1/2 by Robert Field
- "T_RadiusDamage()" workaround 2/2 by Patrick Martin
- Workaround for GLQuake "Invalid skin #<number>" message and dead bodies colors by Robert Field

## Workarounds for Quake[C] bugs

"ftos()" workaround
"ftos()" returns strings with leading spaces when the converted value has decimals, also "ftos()" cuts if off after the first decimal (e.g. " 0.9" for 0.93).
You can check for this behaviour and set a flag depending on the result, so you can recognize it in your modifications.
Define a float variable for this in "Defs.qc", I prefer "correct_ftos" because the name says it all.
Put the following code at the end of "worldspawn()" in "World.qc", so each time a new map is loaded the quake.exe will be tested for this bug.
You should also round/floor/ceil values before printing if necessary, otherwise you get weird looking displays (like in CTF: "1.193546 seconds left").

    "World.qc"

    void() worldspawn =
    {
    ...
    // 1997-12-25 ftos() workaround by Maddes  start
    	local string test_ftos;

    	test_ftos = ftos(0.93);			// convert to string
    	dprint("0.93 was converted by ftos() to \"");
    	dprint(test_ftos);
    	dprint("\"\n");

    	if (test_ftos == "0.93" || test_ftos == " 0.93" || test_ftos == "  0.93")
    	{
    		dprint("ftos() IS WORKING CORRECTLY! Yippie!\n");
    		correct_ftos = 1;
    	}
    	else
    	{
    		dprint("ftos() delivers wrong strings. Take care! :(\n");
    		correct_ftos = 0;
    	}
    // 1997-12-25 ftos() workaround by Maddes  end
    };

"cvar_set()" workaround
"cvar_set()" sets a cvar to zero when the given string has leading spaces. In conjunction with the "ftos() bug" you can not save a cvar, change it for a reason and restore its previous value afterwards.
You can check for this behaviour and set a flag depending on the result, so you can recognize it in your modifications.
Define a float variable for this in "Defs.qc", I prefer "correct_cvars" because the name says it all.
Make sure you test this behaviour with a server variable which will be changed by yourself, I prefer "sv_aim" because I always use my "Auto-aim toggle" in patches (see below). Also you have to make sure you test with a decimal value.
Put the following code at the end of "worldspawn()" in "World.qc", so each time a new map is loaded the quake.exe will be tested for this bug.

    "World.qc"

    void() worldspawn =
    {
    ...
    // 1997-12-25 cvar_set() workaround by Maddes  start
    	local float save_aim;
    	local float test_aim_1;
    	local float test_aim_2;
    	local string set_aim;

    	save_aim = cvar("sv_aim");		// save sv_aim as float

    	cvar_set("sv_aim", "0.93");		// set sv_aim for testing
    	test_aim_1 = cvar("sv_aim");		// get sv_aim as float
    	set_aim = ftos(test_aim_1);		// convert to string
    	cvar_set("sv_aim", set_aim);		// reset sv_aim with string
    	test_aim_2 = cvar("sv_aim");		// get sv_aim again as float
    	dprint("\"sv_aim\" was set by cvar_set() to \"");
    	dprint(ftos(test_aim_2));
    	dprint("\"\n");

    	if (test_aim_2 == test_aim_1)		// still the same?
    	{
    		dprint("Saving and recalling cvars IS WORKING! Yippie!\n");
    		set_aim = ftos(save_aim);
    		cvar_set("sv_aim", set_aim);	// reset to previous value
    		correct_cvars = 2;
    	}
    	else if (test_aim_2 == 0.9)		// only ftos() error?
    	{
    		dprint("Saving and recalling cvars is NEARLY working! Better than nothing!\n");
    		set_aim = ftos(save_aim);
    		cvar_set("sv_aim", set_aim);	// reset to previous value
    		correct_cvars = 1;
    	}
    	else
    	{
    		dprint("Have to use fix values for setting cvars! :(\n");
    		cvar_set("sv_aim", "0.93");		// set to id's standard
    		correct_cvars = 0;
    	}
    // 1997-12-25 cvar_set() workaround by Maddes  end
    };

These workarounds will help you to make your patch work with and without a bugfixed quake.exe, but don't forget to code for both situations.
I also put those workarounds in id's original zip archive, but decide on your own.

Incorrect "trigger_teleport" workaround
As there was an incorrect "trigger_teleport" entity left in E4M5 (above the exit to the secret level), servers crash when a player touches it (see left picture below).
To make triggers visible, go into "Subs.qc" and comment out "self.modelindex = 0;" and "self.model = "";" in the "InitTrigger()" function. At the secret exit of E4M5 you'll see it (see right picture below).
You wonder how to get there? - Use noclip, a rocket/grenade jump or the grappling hook. Note that it's only touchable from below, the ring of the teleporter protects it from being touched from above. You can also disable "trigger_changelevel" entities by placing a "return" statement at the begin of "trigger_changelevel()" in "Client.qc", which will let you swim in the teleporter.
The server crashes only because of the "objerror()" function call. This function stops the server, displays a error message and prints the edict data of "self".
If an incorrect "trigger_teleport" entity is detected, all information can also be displayed with normal functions (developer messages and print edict). You should leave the "teleport_touch()" function after this, because you never know where the player will land. Maybe he will be teleported out of the map, or get stuck in a wall, floor or ceiling.
You may also delete the entity, if it's not needed or not corrected by your addon, and that the addon does not create (incorrect) "trigger_teleport" entities on the fly.
Fixes for START, E4M5 and E4M8 are available for download

    "Triggers.qc"

    void() teleport_touch =
    {
    ...
    	SUB_UseTargets ();

    // 1998-07-07 incorrect teleport workaround by Maddes  start
    /*
    // put a tfog where the player was
    	spawn_tfog (other.origin);
    */
    // 1998-07-07 incorrect teleport workaround by Maddes  end

    	t = find (world, targetname, self.target);
    // 1998-07-07 incorrect teleport workaround by Maddes  start
    	if ( (!t)
    	|| (t.classname != "info_teleport_destination") && (t.classname != "misc_teleporttrain"))
    //		objerror ("couldn't find target");
    	{
    		dprint ("OBJECT ERROR in teleport_touch():\n");
    		dprint ("couldn't find target\n");
    //		eprint (self);	// not a developer message :(
    //		remove (self);	// be careful it's not necessary
    //		dprint ("OBJECT REMOVED\n");
    		return;		// otherwise player lands anywhere/gets stucked
    	}

    // put a tfog where the player was
    	spawn_tfog (other.origin);
    // 1998-07-07 incorrect teleport workaround by Maddes  end

    // spawn a tfog flash in front of the destination
    	makevectors (t.mangle);
    	org = t.origin + 32 * v_forward;
    ...
    };

An excerpt from the map part of E4M5's bsp file shows that the target is not a "info_teleport_destination" enitity.

    {
    "classname" "trigger_teleport"
    "target" "t178"
    "model" "*95"
    }
    {
    "classname" "info_null"
    "origin" "796 2212 260"
    "targetname" "t178"
    }

"T_RadiusDamage()" workaround 1/2 by Robert Field
To avoid that other players do not get hurt by splash damage, when the attacker dies through it, you have to hurt the attacker after all other players.
In "Combat.qc" find the "T_RadiusDamage" function and change the code as follwoing:

    "Combat.qc"

    void(entity inflictor, entity attacker, float damage, entity ignore) T_RadiusDamage =
    {
    	local	float 	points;
    	local	entity	head;
    	local	vector	org;
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  start
    	local	float	attacker_damaged;
    	local	float	attacker_points;
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  end

    	head = findradius(inflictor.origin, damage+40);

    	while (head)
    	{
    		if (head != ignore)
    		{
    			if (head.takedamage)
    			{
    				org = head.origin + (head.mins + head.maxs)*0.5;
    				points = 0.5*vlen (inflictor.origin - org);
    				if (points < 0)
    					points = 0;
    				points = damage - points;
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  start
    /*
    				if (head == attacker)
    					points = points * 0.5;
    */
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  end
    				if (points > 0)
    				{
    					if (CanDamage (head, inflictor))
    					{
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  start
    						if (head != attacker)
    						{
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  end
    						// shambler takes half damage from all explosions
    						if (head.classname == "monster_shambler")
    							T_Damage (head, inflictor, attacker, points*0.5);
    						else
    							T_Damage (head, inflictor, attacker, points);
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  start
    						}
    						else
    						{
    							attacker_damaged = TRUE;
    							attacker_points = points * 0.5;
    						}
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  end
    					}
    				}
    			}
    		}
    		head = head.chain;
    	}

    // 1998-07-07 T_RadiusDamage workaround by Robert Field  start
    	if (attacker_damaged)
    		T_Damage (attacker, inflictor, attacker, attacker_points);
    // 1998-07-07 T_RadiusDamage workaround by Robert Field  end
    };

"T_RadiusDamage()" workaround 2/2 by Patrick Martin
Everything which causes radius damage when dying (e.g. box explosions) must call "T_RadiusDamage()" in the second death frame or later, to avoid recursive calls.
Only the boxes call "T_RadiusDamage()" immediately. The tarbaby is working 100%.
Let the boxes explode and hurt others in the next frame:

    "Misc.qc"

    // 1998-08-08 T_RadiusDamage workaround by Patrick Martin  start
    void() barrel_damage =
    {
    	T_RadiusDamage (self, self, 160, world, "");	// 1998-07-24 Wrong obituary messages fix by Zoid
    	sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM);
    	particle (self.origin, '0 0 0', 75, 255);

    	self.origin_z = self.origin_z + 32;
    	BecomeExplosion ();
    };
    // 1998-08-08 T_RadiusDamage workaround by Patrick Martin  end

    void() barrel_explode =
    {
    	self.takedamage = DAMAGE_NO;
    	self.classname = "explo_box";

    // 1998-08-08 T_RadiusDamage workaround by Patrick Martin  start
    /*
    	// did say self.owner
    	T_RadiusDamage (self, self, 160, world, "");	// 1998-07-24 Wrong obituary messages fix by Zoid
    	sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM);
    	particle (self.origin, '0 0 0', 75, 255);

    	self.origin_z = self.origin_z + 32;
    	BecomeExplosion ();
    */

    	self.nextthink = time + 0.1;
    	self.think = barrel_damage;
    // 1998-08-08 T_RadiusDamage workaround by Patrick Martin  end
    };

Workaround for GLQuake "Invalid skin #<number>" message and dead bodies colors by Robert Field
This workaround should work with any existing mod, it was originally for the Frogbot mod.
It was put in a form that can be easily interfaced to other mods.
But this workaround is not a final solution, for the following reasons:

- You have to set "maxplayers" to the number of players plus 4 to see colored bodies
- If you change your colors during gameplay, your corpses will not change
- The "Invalid skin #<number> workaround will only work if the clients have a "player.mdl" file with the skin number in the player model (eyes and head models are used with skin 0). When players get the eyes or use a player.mdl which don't have heads (besides skin 0), then the invalid skin error is now avoided.
- Unfortunately, the shirt color is not accessable from QuakeC, though if shirt colors are set by your mod code then it is easy to change WriteByte(MSG_ALL, (ent.team - 1) * 17); in "skinfix.qc" to WriteByte(MSG_ALL, (ent.shirt * 16 + ent.team - 1)); to set proper shirt colors.

First create a new file called "SkinFix.qc" which will contain some new and replacement functions for "InitBodyQue()" and "CopyToBodyQue()" in "World.qc".

    "SkinFix.qc"

    /*
    These functions give dead bodies color and avoid the "Invalid skin" message under GLQuake (currently v0.97)

    The first maxplayers entities (ie. the entities besides world that exist at
    the start of worldspawn) are used by clients when they connect.
    The client entities are the only ones that have colors for player models in
    GLQuake. Spare client entities can be used for dead bodies.
    The approach of this workaround is to use client entities for player models where
    possible and use normal entities for head gibs and eyes.
    Hence head gibs and eyes always have skin 0, thus avoiding the invalid player
    skin # error.
    If there is not 4 spare client entities then some bodies use normal entities
    and hence don't have color in GLQuake.
    */
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    //            file added

    /*
    ============
    PostThink

    Displays alternative entities for players or bodies if necessary each frame.
    (the actual player or body entity in these cases are not rendered)
    P.S.: don't mix up with PlayerPostThink() in client.qc
    ============
    */
    void() PostThink =
    {
    	self.nextthink = 0.001;	// call PostThink every frame

    	// loop through players linked list and display their eye or head gib model if necessary
    	self = find(world, classname_, "player");
    	while (self)
    	{
    		if (self.display_ent.modelindex)
    		{
    			// self.display_ent is a non-client entity
    			setorigin(self.display_ent, self.origin);
    			self.display_ent.angles = self.angles;
    			self.display_ent.effects = self.effects;
    		}
    		self = find(self, classname_, "player");
    	}

    	// loop through bodies linked list and display their player model if necessary
    	self = first_bodyque;
    	while (self)
    	{
    		// self.display_ent is a spare client entity or world
    		// self.display_ent.modelindex == 0 if a non-player model is being used, and self.display_ent is a spare client
    		// self.display_ent.modelindex == 1 if self.display_ent == world
    		if (self.display_ent.modelindex > 1)
    			setorigin(self.display_ent, self.origin);
    		self = self.owner;
    	}
    };

    /*
    ============
    FindClientBodyQue

    Assigns a spare client entity to bodyque_entry.
    If none spare then assign world.
    Start search for client entity at bodyque_client.
    ============
    */
    void(entity bodyque_entry) FindClientBodyQue =
    {
    	// post_think is the first entity after the client entities
    	while (bodyque_client != post_think)
    	{
    		// check if bodyque_client is currently used by a client or body
    		if (bodyque_client.classname_ != "player" && bodyque_client.classname_ != "body")
    		{
    			// bodyque_client is spare
    			bodyque_entry.display_ent = bodyque_client;
    			bodyque_client.classname_ = "body";
    			bodyque_client.score_pos = score_pos_;
    			return;
    		}
    		// get next bodyque_client and increment its index
    		score_pos_ = score_pos_ + 1;
    		bodyque_client = nextent(bodyque_client);
    	}

    	bodyque_entry.display_ent = world;	// no spare client entities
    };

    /*
    ============
    AssignBodies

    Assigns spare client entities to the 4 body entities.
    If none spare then assign world.
    This function is called whenever there is a change in the connected client entities list.
    ============
    */
    void() AssignBodies =
    {
    	local entity bodyque_entry;

    	// get first bodyque_client (with index 0)
    	score_pos_ = 0;
    	bodyque_client = nextent(world);

    	// loop through bodies linked list and assign client entities if possible
    	bodyque_entry = first_bodyque;
    	while (bodyque_entry)
    	{
    		if (bodyque_entry.classname_ != "body")
    			FindClientBodyQue(bodyque_entry);
    		bodyque_entry = bodyque_entry.owner;
    	}
    };

    /*
    ============
    InitBodyQue

    The bodies form a linked list (in the original id code they form a ring).
    Called at the start of worldspawn so that post_think is the first entity after the client entities.
    ============
    */
    void() InitBodyQue =
    {
    	post_think = spawn();
    	post_think.nextthink = 0.001;
    	post_think.think = PostThink;

    	bodyque_head = first_bodyque = spawn();
    	bodyque_head.owner = spawn();
    	bodyque_head.owner.owner = spawn();
    	bodyque_head.owner.owner.owner = spawn();
    };

    /*
    ============
    CopyToBodyQue

    Called whenever the original id CopyToBodyQue was called.
    ============
    */
    void(entity ent) CopyToBodyQue =
    {
    	local entity bodyque_entry;	// the entity that renders the body

    	if (ent.modelindex)
    	{
    		// ent is using the player model
    		if (bodyque_head.display_ent)
    		{
    			// bodyque_head is using a spare client entity (ie. bodyque_entry)
    			bodyque_entry = bodyque_head.display_ent;
    			bodyque_head.modelindex = 0;	// don't render bodyque_head

    			// set body colors for GLQuake (ignored by normal Quake since it takes notice of .colormap below)
    			WriteByte(MSG_ALL, MSG_UPDATECOLORS);
    			WriteByte(MSG_ALL, bodyque_entry.score_pos);	// the index of the client entity bodyque_entry
    			WriteByte(MSG_ALL, (ent.team - 1) * 17);	// GLQuake color (ent.team - 1)
    			// if ent has a shirt color ent.shirt then replace this last line by
    			// WriteByte(MSG_ALL, ent.shirt * 16 + (ent.team - 1));
    			setorigin (bodyque_entry, ent.origin);	// just in case PostThink has already been called this frame
    		}
    		else
    		{
    			// bodyque_head doesn't have a client entity so use itself (its color won't work in GLQuake though)
    			bodyque_entry = bodyque_head;
    		}

    		bodyque_entry.skin = ent.skin;
    		bodyque_entry.modelindex = ent.modelindex;
    	}
    	else
    	{
    		// ent is not using the player model
    		if (bodyque_head.display_ent)
    			bodyque_head.display_ent.modelindex = 0;
    		// don't render the client entity bodyque_head.display_ent
    		bodyque_entry = bodyque_head;
    		bodyque_entry.skin = 0;
    		bodyque_entry.modelindex = ent.display_ent.modelindex;
    	}

    	// set bodyque_entry.model to != ""
    	bodyque_entry.model = "/";
    	// set body colors for normal Quake (ignored by GLQuake)
    	bodyque_entry.colormap = ent.colormap;
    	// copy info display info of ent
    	bodyque_entry.angles = ent.angles;
    	bodyque_entry.frame = ent.frame;

    	// copy physics info of ent
    	bodyque_head.velocity = ent.velocity;
    	bodyque_head.flags = 0;
    	setorigin (bodyque_head, ent.origin);
    	setsize (bodyque_head, ent.mins, ent.maxs);
    	bodyque_head.movetype = MOVETYPE_TOSS;

    	// move along bodyque_head (note: using a linked list not a ring)
    	bodyque_head = bodyque_head.owner;
    	if (!bodyque_head)
    		bodyque_head = first_bodyque;
    };

    /*
    ============
    SkinFixConnect

    Called when client connects.
    Assigns client an entity which is used for its head gib or eye model.
    ============
    */
    void(entity client) SkinFixConnect =
    {
    	client.classname_ = "player";
    	client.display_ent = spawn();
    	// set client.display_ent to != ""
    	client.display_ent.model = "/";
    };

    /*
    ============
    SkinFixDisConnect

    Called when client disconnects.
    Does garbage collection for client.display_ent.
    ============
    */
    void(entity client) SkinFixDisConnect =
    {
    	// since removing client.display_ent need to copy it if it is being rendered
    	if (client.display_ent.modelindex)
    		CopyToBodyQue(client);

    	// must garbage collect client.display_ent since a new client will have client.display_ent == world
    	remove(client.display_ent);
    };

    /*
    ============
    Set_modelindex_0

    Called when client is not being rendered (eg. intermission or observer mode)
    ============
    */
    void(entity client) Set_modelindex_0 =
    {
    	client.display_ent.modelindex = 0;
    };

    /*
    ============
    Set_modelindex_player

    Called after client has been given a player model.
    (client.modelindex must have been set)
    ============
    */
    void(entity client) Set_modelindex_player =
    {
    	if (client.display_ent.modelindex)
    	{
    		// client.display_ent was being rendered
    		client.display_ent.modelindex = 0;	// don't render client.display_ent

    		// switch to client viewport
    		msg_entity = client;
    		WriteByte(MSG_ONE, SVC_SETVIEWPORT);
    		WriteEntity(MSG_ONE, client);
    	}
    };

    /*
    ============
    Set_modelindex_non_player

    Called after client has been given a non-player model.
    (client.modelindex must have been set)
    ============
    */
    void(entity client) Set_modelindex_non_player =
    {
    	if (client.display_ent.modelindex != client.modelindex)
    	{
    		// client.modelindex is the desired non-player model which client.display_ent will be rendering
    		client.display_ent.modelindex = client.modelindex;	// render client.display_ent

    		// switch to client.display_ent viewport
    		msg_entity = client;
    		WriteByte(MSG_ONE, SVC_SETVIEWPORT);
    		WriteEntity(MSG_ONE, client.display_ent);
    	}
    	client.modelindex = 0;	// don't render client
    };

Now you have to inform the compiler about the new file by putting the following line after "Defs.qc" in "Progs.src".

    "Progs.src"

    skinfix.qc		// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field

Some new variables will be needed to be defined in "Defs.qc".

    "Defs.src"

    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  start
    //
    // skinfix.qc
    //

    // protocol bytes
    float SVC_SETVIEWPORT = 5;
    float MSG_UPDATECOLORS = 17;

    entity post_think;	// used to call PostThink every frame (after player physics),
    			// and is the first entity after the client entities
    entity bodyque_head;	// the next bodyque entity to be used
    entity first_bodyque;	// the start of the bodyque linked list
    entity bodyque_client;	// used to find a spare client entity to be used as a body
    float score_pos_;	// the entity index of bodyque_client

    .entity display_ent;	// the alternative display entity of a client or body
    .float score_pos;	// the entity index
    .string classname_; 	// used to avoid conflict with .classname, but not strictly necessary
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  end

After this you have to add the new functionality to the code:

- Whenever a player entity is made blank include a call to "Set_modelindex_0()" after it
- Whenever a player entity is set to a player model include a call to "Set_modelindex_player()" after it
- Whenever a player entity is set to a non-player model include a call to "Set_modelindex_non_player()" after it
- At the end of "ClientConnect()" in "Client.qc" include "SkinFixConnect(self);" and "AssignBodies();"
- At the end of "ClientDisconnect()" in "Client.qc" include "SkinFixDisConnect(self);"
- Comment out "InitBodyQue()" and "CopyToBodyQue()" in "World.qc"

    "Client.qc"

    void() execute_changelevel =
    {
    ...
    	while (other != world)
    	{
    		...
    		other.modelindex = 0;
    		Set_modelindex_0(other);	// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    		setorigin (other, pos.origin);
    		other = find (other, classname, "player");
    	}

    	WriteByte (MSG_ALL, SVC_INTERMISSION);
    };
    ...
    void() ClientKill =
    {
    ...
    	self.modelindex = modelindex_player;
    	Set_modelindex_player(self);	// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    ...
    };
    ...
    void() CheckPowerups =
    {
    ...
    // invisibility
    	if (self.invisible_finished)
    	{
    		...
    		// use the eyes
    		self.frame = 0;
    		self.modelindex = modelindex_eyes;
    		Set_modelindex_non_player(self);	// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    	}
    	else
    	{	// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    		self.modelindex = modelindex_player;	// don't use eyes
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  start
    		Set_modelindex_player(self);
    	}
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  end

    // invincibility
    ...
    };
    ...
    void() ClientConnect =
    {
    ...
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  start
    	SkinFixConnect(self);
    	AssignBodies();
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  end
    };
    ...
    void() ClientDisconnect =
    {
    ...
    	SkinFixDisConnect(self);	// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    };

    "Player.qc"

    void(string gibname, float dm) ThrowHead =
    {
    	setmodel (self, gibname);

    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  start
    	if (self.classname == "player")
    		Set_modelindex_non_player(self);
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  end
    ...
    };
    ...
    void() PlayerDie =
    {
    ...
    	self.modelindex = modelindex_player;	// don't use eyes
    	Set_modelindex_player(self);		// 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field
    ...
    };

    "World.qc"

    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  start
    /*
    void() InitBodyQue =
    {
    ...
    };

    // make a body que entry for the given ent so the ent can be
    // respawned elsewhere
    void(entity ent) CopyToBodyQue =
    {
    ...
    };
    */
    // 1998-06-21 Invalid skin and GLQuake body color workaround by Robert Field  end

a Quake Info Pool page
© 1997-2022 by Maddes