Quake Info Pool - Classic Quake - QuakeC Enhancements

Back | Last changes: 1999-08-23 | Contact Maddes

# QuakeC Enhancements

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

(discontinued, check out URQP source for more information)

## Index

- "NoExit" without dying (inspired by Robert "Frog" Field)
- Message Of The Day
- Displaying info on respawn and level changes
- Auto-aim toggle for every client
- Multiple skins support for all players
- All SVC commands by Yun Zheng "Zhenga" Hu
- Centerprint with multiple strings by Yun Zheng "Zhenga" Hu
- Player drops backpack on suicide
- Smooth plat movement by Matt Barnett
- Better performance in handling of powerups
- Drowning doesn't hurt armor
- Improved weapon switching on pickups

"NoExit" without dying
(inspired by Robert "Frog" Field)
Sometimes it is really annoying when you loose a frag, because you accidentally stepped into an exit during a heated firefight and "noexit" is enabled. :(
The following code disallows players to exit a map, but he isn't killed if someone tries to and "noexit" is set to 3 or 4 (similar to 1 and 2).
Here's the code:

    "Client.qc"

    void() changelevel_touch =
    {
    ...
    	if ((cvar("noexit") == 1) || ((cvar("noexit") == 2) && (mapname != "start")))
    	{
    		T_Damage (other, self, self, 50000);
    		return;
    	}
    // 1998-07-16 Noexit without dying by Maddes (inspired by Robert Field)  start
    	if ((cvar("noexit") == 3) || ((cvar("noexit") == 4) && (mapname != "start")))
    	{
    		return;
    	}
    // 1998-07-16 Noexit without dying by Maddes (inspired by Robert Field)  end
    ...
    };

Message Of The Day
The message of the day (MOTD) will be displayed when a new client connects and after level changes. It's just for welcoming the clients, giving general information or rejecting client-side bots, hence normally "centerprint()" is used for it.
First create a new file called "Motd.qc" which will contain the displaying and managing functions. Remember that "centerprint()" needs all the text in one argument, and that the clients have a display delay of around 0.5 seconds when connecting.
Normally most functions are for players only, so don't forget to check the entity class.
Include the features, your name, email address and homepage (if available) in your MOTD.
The file should look like this:

    "Motd.qc"

    /*
    This file handles the Message Of The Day (MOTD)
    */
    // 1998-07-17 Reject client bots by Maddes
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes
    //            adding initializing and changing function
    //            expanding display function
    // 1997-12-24 Message Of The Day (MOTD) by Maddes
    //            file added
    //            adding printing function of MOTD

    void(entity self) DisplayMOTD =
    {
    	if (self.classname != "player")
    		return;

    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  start
    	// determine which text to display
    	if (self.motdtext == 1)
    	{
    		centerprint(self, "No Bots Please\n");	// 1998-07-17 Reject client bots by Maddes

    	}
    	else if (self.motdtext == 0)
    	{
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  end
    		centerprint(self,"ULTIMATE REGULAR QUAKE PATCH\n* Auto-aim toggle *\n* Skins *\n* Noexit 3/4 without dying  *\n\nby Maddes\nVersion 1.02\n1998-07-17\n\nhttp://www.quake-info-pool.net/\n");
    	}	// 1998-07-17 Series of Message Of The Day (MOTD) by Maddes
    };

    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  start
    void(entity self) InitMOTD =
    {
    	self.motdtext = 2;		// quantity of MOTD messages
    	self.motdtime = time - 0.01;	// the past is always to late
    };

    void(entity self) ChangeMOTD =
    {
    	// next MOTD
    	self.motdtext = self.motdtext - 1;	// counting down to zero

    	// define timeframe of MOTD being displayed
    	if (self.motdtext == 1)
    	{
    		self.motdtime = time + 0.2;	// 1998-07-17 Reject client bots by Maddes
    	}
    	else if (self.motdtext == 0)
    	{
    		self.motdtime = time + 1.5;
    	}
    };
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  end

Now you have to inform the compiler about the new file by putting the following line at the end of "Progs.src".

    "Progs.src"

    motd.qc			// 1997-12-24 Message Of The Day (MOTD) by Maddes

After this you have to declare the functions in "Defs.qc", so you can call them from the other files.
You also need a timeflag which tells you how long to display the MOTD, and a flag which MOTD to display. This has to be separated for each client, hence adding it to all entities is the best solution. A variable is added to the entity structure when the type has a leading point.
Mentioning the files which use the variable helps when changing something.
Here's the result:

    "Defs.qc"

    // 1997-12-24 Message Of The Day (MOTD) by Maddes  start
    //
    // client.qc / motd.qc
    //
    .float motdtime;				// add timeflag for MOTD to entities
    .float motdtext;				// 1998-07-17 Series of Message Of The Day (MOTD) by Maddes
    						// add textcounter for MOTD to entities

    void(entity self) DisplayMOTD;
    void(entity self) InitMOTD;			// 1998-07-17 Series of Message Of The Day (MOTD) by Maddes
    void(entity self) ChangeMOTD;			// 1998-07-17 Series of Message Of The Day (MOTD) by Maddes
    // 1997-12-24 Message Of The Day (MOTD) by Maddes  end

The handling of the MOTD is done in "Client.qc".
Each time a client connects (also after level changes he reconnects) the function "ClientConnect()" is executed, here you have to initialize all variables.
Later, when the client is connected, on each frame "PlayerPostThink()" is executed. Just check if the time is up for the current MOTD, then select the next one, if available, and set a new time for it. After this, if time for current MOTD isn't reached, you call the display function.
You should always test your MOTD in 320x200.
To finish this modification use this code:

    "Client.qc"

    void() PlayerPostThink =
    {
    ...
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  start
    	if ((self.motdtext) && (self.motdtime < time))	// change to next when not last MOTD and time is reached
    	{
    		ChangeMOTD(self);
    	}
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  end
    // 1997-12-24 Message Of The Day (MOTD) by Maddes  start
    	if (self.motdtime >= time)			// display until time is reached
    	{
    		DisplayMOTD(self);
    	}
    // 1997-12-24 Message Of The Day (MOTD) by Maddes  end
    };
    ...
    void() ClientConnect =
    {
    ...
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  start
    	InitMOTD(self);				// start with first MOTD
    //	self.motdtime = time + 1.5;		// 1997-12-24 Message Of The Day (MOTD) by Maddes
    						//            display some seconds so player can read
    // 1998-07-17 Series of Message Of The Day (MOTD) by Maddes  end
    };

Displaying info on respawn and level changes
This is used to tell the player which settings are in effect for him and which impulses he can use to change them. Also "sprint()" is used so the player can reread the messages in the console.
The QuakeC code is similar to the one of the MOTD, you have to define a timeflag within the entity structure and check for it in the function "PlayerPostThink()".
The timeflag is not only set in "ClientConnect()" but also in "respawn()", and unlike to the MOTD the messages are printed once after the time has reached.
Here's the solution:

    "Defs.qc"

    // 1997-12-24 Message on respawn by Maddes  start
    //
    // client.qc
    //
    .float spawn_info;				// add timeflag for displaying respawn info to entities
    // 1997-12-24 Message on respawn by Maddes  end

    "Client.qc"

    void() respawn =
    {
    ...
    	self.spawn_info = time + 0.1;			// 1997-12-24 Message on respawn by Maddes  wait a game tick
    };
    ...
    void() PlayerPostThink =
    {
    ...
    // 1997-12-24 Message on respawn by Maddes  start
    	if (self.spawn_info)			// time set?
    	{
    		if (self.spawn_info <= time)	// print after time is reached
    		{
    			self.spawn_info = 0;	// clear time

    			local string text;

    			// here come all the print functions

    		}
    	}
    // 1997-12-24 Message on respawn by Maddes  end
    };
    ...
    void() ClientConnect =
    {
    ...
    	self.spawn_info = time + 1;		// 1997-12-24 Message on respawn by Maddes
    						//            wait some seconds so client is ready and player can read the message
    };

Auto-aim toggle for every client
Once again you have to define a new variable for entities to separate the auto-aim toggle for each client.
Also you need to define which impulse toggles it.
So put something like this in "Defs.qc":

    "Defs.qc"

    // 1997-12-26 auto-aim toggle by Maddes  start
    //
    // weapons.qc / client.qc
    //
    float IMPULSE_TOGGLE_AIM = 250;			// setting impulse for toggle

    .float autoaim_off;				// add flag for auto-aim to entities
    // 1997-12-26 auto-aim toggle by Maddes  end

In "Weapons.qc" you have to recognize the player's auto-aim toggle when firing.
To toggle it on the given impulse is done in the "ImpulseCommands()" function. Set it there from 0 to 1 or reverse, this can be done in one line like "x = 1 - x" (1-0=1, 1-1=0 :).
In the fire functions you have to set "sv_aim" to 1 when the client doesn't want auto-aiming and set it back to its previous value after shooting. Here you have to remember the "ftos()" and "cvar_set()" bugs (see workarounds above). Make sure the firing function does NOT call another function and return after you had set "sv_aim" or that it end before you restore "sv_aim"!!!
The Thunderbolt is the only weapon which doesn't use the "aim()" function.
Here's the solution for "Weapons.qc":

    "Weapons.qc"

    void() W_FireShotgun =
    {
    	local vector dir;

    // 1997-12-26 auto-aim toggle by Maddes  start
    	local float save_aim;
    	local string set_aim;

    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			save_aim = cvar("sv_aim");
    			set_aim = ftos(save_aim);
    		}

    		cvar_set("sv_aim", "1");		// disable auto-aiming by setting sv_aim to 1
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			cvar_set("sv_aim", set_aim);	// reset sv_aim to previous setting
    		}
    		else
    		{
    			cvar_set("sv_aim", "0.93");	// set sv_aim to id's standard of 0.93
    		}
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void() W_FireSuperShotgun =
    {
    	local vector dir;

    	if (self.currentammo == 1)
    	{
    		W_FireShotgun ();
    		return;
    	}

    // 1997-12-26 auto-aim toggle by Maddes  start
    	local float save_aim;
    	local string set_aim;

    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			save_aim = cvar("sv_aim");
    			set_aim = ftos(save_aim);
    		}

    		cvar_set("sv_aim", "1");		// disable auto-aiming by setting sv_aim to 1
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			cvar_set("sv_aim", set_aim);	// reset sv_aim to previous setting
    		}
    		else
    		{
    			cvar_set("sv_aim", "0.93");	// set sv_aim to id's standard of 0.93
    		}
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void() W_FireRocket =
    {
    	local	entity missile, mpuff;

    // 1997-12-26 auto-aim toggle by Maddes  start
    	local float save_aim;
    	local string set_aim;

    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			save_aim = cvar("sv_aim");
    			set_aim = ftos(save_aim);
    		}

    		cvar_set("sv_aim", "1");		// disable auto-aiming by setting sv_aim to 1
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			cvar_set("sv_aim", set_aim);	// reset sv_aim to previous setting
    		}
    		else
    		{
    			cvar_set("sv_aim", "0.93");	// set sv_aim to id's standard of 0.93
    		}
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void() W_FireGrenade =
    {
    	local	entity missile, mpuff;

    // 1997-12-26 auto-aim toggle by Maddes  start
    	local float save_aim;
    	local string set_aim;

    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			save_aim = cvar("sv_aim");
    			set_aim = ftos(save_aim);
    		}

    		cvar_set("sv_aim", "1");		// disable auto-aiming by setting sv_aim to 1
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			cvar_set("sv_aim", set_aim);	// reset sv_aim to previous setting
    		}
    		else
    		{
    			cvar_set("sv_aim", "0.93");	// set sv_aim to id's standard of 0.93
    		}

    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void() W_FireSuperSpikes =
    {
    	local vector	dir;
    	local entity	old;

    // 1997-12-26 auto-aim toggle by Maddes  start
    	local float save_aim;
    	local string set_aim;

    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			save_aim = cvar("sv_aim");
    			set_aim = ftos(save_aim);
    		}

    		cvar_set("sv_aim", "1");		// disable auto-aiming by setting sv_aim to 1
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			cvar_set("sv_aim", set_aim);	// reset sv_aim to previous setting
    		}
    		else
    		{
    			cvar_set("sv_aim", "0.93");	// set sv_aim to id's standard of 0.93
    		}
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void(float ox) W_FireSpikes =
    {
    	local vector	dir;
    	local entity	old;

    	makevectors (self.v_angle);

    	if (self.ammo_nails >= 2 && self.weapon == IT_SUPER_NAILGUN)
    	{
    		W_FireSuperSpikes ();
    		return;
    	}

    	if (self.ammo_nails < 1)
    	{
    		self.weapon = W_BestWeapon ();
    		W_SetCurrentAmmo ();
    		return;
    	}

    // 1997-12-26 auto-aim toggle by Maddes  start
    	local float save_aim;
    	local string set_aim;

    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			save_aim = cvar("sv_aim");
    			set_aim = ftos(save_aim);
    		}

    		cvar_set("sv_aim", "1");		// disable auto-aiming by setting sv_aim to 1
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.autoaim_off)
    	{
    		if (correct_cvars)
    		{
    			cvar_set("sv_aim", set_aim);	// reset sv_aim to previous setting
    		}
    		else
    		{
    			cvar_set("sv_aim", "0.93");	// set sv_aim to id's standard of 0.93
    		}
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void() ImpulseCommands =
    {
    	if (self.impulse >= 1 && self.impulse <= 8)
    		W_ChangeWeapon ();

    	if (self.impulse == 9)
    		CheatCommand ();
    ...
    // 1997-12-26 auto-aim toggle by Maddes  start
    	if (self.impulse == IMPULSE_TOGGLE_AIM)	// Impulse to toggle auto-aim on/off
    	{
    		self.autoaim_off = 1 - self.autoaim_off;

    		sprint(self,"Auto-Aiming toggled ");
    		if (self.autoaim_off) sprint(self,"off\n");
    		else sprint(self,"on\n");
    	}
    // 1997-12-26 auto-aim toggle by Maddes  end

    	if (self.impulse == 255)
    		QuadCheat ();

    	self.impulse = 0;
    };

Right now the player is able to toggle auto-aiming on or off, but has to redo this each time he respawns or after a level change.
To fix this you have to save the toggle in one of the "Parms" variables, these are the only player variables which remain intact even after a level change.
The "SetChangeParms()" function is used on level changes, "SetNewParms()" is for new clients and for respawn, "respawn()" is for respawn only and "DecodeLevelParms" is for giving the player its previous or new settings. So you have to save it in "SetChangeParms()" plus "respawn()" and to recognize it in "DecodeLevelParms()". DO NOT save it in "SetNewParms()" and clear the choosen "Parm" variable in "DecodeLevelParms()", because when a new client connects he will get the setting from another active client.
And finally you should tell the player if auto-aiming is on or off when he respawns or enters a new level. Use Displaying info on respawn and level changes for this.
Here's the solution:

    "Client.qc"

    void() SetChangeParms =
    {
    ...
    	parm9 = self.armortype * 100;

    	parm16 = self.autoaim_off;		// 1997-12-26 auto-aim toggle by Maddes  save auto-aim on level changes
    };

    void() SetNewParms =
    {
    ...
    	parm9 = 0;

    	// 1997-12-26 auto-aim toggle by Maddes  self.autoaim_off is only saved in parm16 on respawn (see there)
    };

    void() DecodeLevelParms =
    {
    ...
    	self.armortype = parm9 * 0.01;

    // 1997-12-26 auto-aim toggle by Maddes  start
    	self.autoaim_off = parm16;		// remember auto-aim after respawn and level changes
    	parm16 = 0;				// has to be cleared, because new clients get a copy of the "Parm..." variables from another client
    						// and all clients should start with auto-aiming on
    // 1997-12-26 auto-aim toggle by Maddes  end
    };
    ...
    void() respawn =
    {
    	if (coop)
    	{
    ...
    		// get the spawn parms as they were at level start
    		setspawnparms (self);
    		parm16 = self.autoaim_off;		// 1997-12-26 auto-aim toggle by Maddes  save auto-aim on respawn
    		// respawn
    		PutClientInServer ();
    	}
    	else if (deathmatch)
    	{
    ...
    		// set default spawn parms
    		SetNewParms ();
    		parm16 = self.autoaim_off;		// 1997-12-26 auto-aim toggle by Maddes  save auto-aim on respawn
    		// respawn
    		PutClientInServer ();
    	}
    	else
    	{	// restart the entire server
    		localcmd ("restart\n");
    	}

    	self.spawn_info = time + 0.1;			// 1997-12-24 Message on respawn by Maddes  wait a game tick
    };
    ...
    void() PlayerPostThink =
    {
    ...
    // 1997-12-24 Message on respawn by Maddes  start
    	if (self.spawn_info)			// time set?
    	{
    		if (self.spawn_info <= time)	// print after time is reached
    		{
    			self.spawn_info = 0;	// clear time

    			local string text;

    			// here come all the print functions

    		// 1997-12-26 auto-aim toggle by Maddes  start
    			sprint(self,"\"impulse ");
    			text = ftos(IMPULSE_TOGGLE_AIM);
    			sprint(self,text);
    			sprint(self,"\" to toggle auto-aim\n");
    		// 1997-12-26 auto-aim toggle by Maddes  end

    		// 1997-12-26 auto-aim toggle by Maddes  start
    			sprint(self,"Auto-Aiming is ");
    			if (self.autoaim_off) sprint(self,"off\n");
    			else sprint(self,"on\n");
    		// 1997-12-26 auto-aim toggle by Maddes  end
    		}
    	}
    // 1997-12-24 Message on respawn by Maddes  end
    };

Multiple skins support for all players
A long time ago Toni Wilen posted a QuakeC workaround for displaying skins correctly in GLQuake. After every ".skin = ...;" on a player you should also clear ".colormap" and ".modelindex", some how this helped a lot with the old GLQuake. With the current version (v0.97) of GLQuake this is no more necessary, although there are still some problems with dead players and skins. Read more about this on my bugs page.
By the way, clearing ".colormap" avoids seeing player colors and ".modelindex" is changed a lot during the game, so clearing it only after ".skin = ..." doesn't last long.
Now to the patch:
First you have to define a new entity variable again, this will let you save the selected skin separated from the current shown skin, so skins depending on an event (like wearing biosuit, etc.) are possible.
You need some functions to let the player choose his skin and let him know which skin is currently selected. The skin selection should be able to skip skins by a given value, to avoid event skins and only allow skins in a given range.
Also a function which returns the name of a skin is fine, but not necessary because the skins on the clients may vary.
The routines select skin #0 when you pickup the ring of shadows, as the eyes model doesn't need multiple skins. When a player dies or suicides he gets his selected skin back.
On the finale intermission after killing Shub the shown player model has correct colors and skin.

    "Skins.qc"

    /*
    This file handles skin selections
    */
    // 1997-12-30 skin support by Maddes
    //            file added
    //            skin select and print functions

    void(entity ent, float prev_or_next) ChangeSkin =
    {
    	local float skin_new;				// For calculating issues
    	local float number;				// For calculating issues
    	local string text;				// For printing issues

    	if (ent.classname != "player")
    		return;

    	skin_new = ent.skin_selected + prev_or_next;	// Add or Subtract the skin-number

    	while ( (skin_new < 0)
    		|| (skin_new >= SKINS_MAX_SUPPORTED) )
    	{
    		if (skin_new < 0)				// Cant be below zero (normally Quake Guy)
    		{
    			skin_new = 0;
    			prev_or_next = 1;			// change direction
    			sprint(ent,"No skins before #1\n");	// Print "No skins before #1" plus new-line
    		}
    		else if (skin_new >= SKINS_MAX_SUPPORTED)	// Cant be equal to maximum skins
    		{
    			skin_new = SKINS_MAX_SUPPORTED - 1;
    			prev_or_next = -1;			// change direction

    			number = SKINS_MAX_SUPPORTED;
    			text = ftos(number);			// Convert float to string for printing number
    			sprint(ent,"Last skin is #");		// Print "Last skin is #<number>" plus new-line
    			sprint(ent,text);
    			sprint(ent,"\n");
    		}
    		else
    		{
    			skin_new = skin_new + prev_or_next;	// Add or Subtract the skin-number
    		}
    	}

    	ent.skin_selected = skin_new;

    	SetSkin(ent);

    	DisplaySkinName(ent);
    };

    void(entity ent) SetSkin =
    {
    	if (ent.invisible_finished)		// eyes model does not need multiple skins
    		ent.skin = 0;
    	else
    		ent.skin = ent.skin_selected;
    };

    void(entity ent) DisplaySkinName =
    {
    	local float number;			// For calculating issues
    	local string text;			// For printing issues

    	if (ent.classname != "player")
    		return;

    	number = ent.skin_selected + 1;		// skin number

    	text = ftos(number);			// Convert float to string for printing number

    	sprint(ent,"Skin #");			// Print "Skin #<number>"
    	sprint(ent,text);

    	text = GetSkinName(ent);
    	if (text)
    	{
    		sprint(ent," \"");		// Print ""<name>""
    		sprint(ent,text);
    		sprint(ent,"\"");
    	}

    	sprint(ent,"\n");			// Print new-line
    };

    string(entity ent) GetSkinName =
    {
    	if (ent.classname != "player")
    		return;

    // Here you enter what skin number goes with what text, just type the name of the skin where you see <name>
    // and don't forget to remove the comment characters "//" from the line
    	if      (ent.skin_selected ==  0) return("Quake Guy");
    	else if (ent.skin_selected ==  1) return("Bot Skill 0 Skin");
    	else if (ent.skin_selected ==  2) return("Bot Skill 1 Skin");
    	else if (ent.skin_selected ==  3) return("Bot Skill 2 Skin");
    	else if (ent.skin_selected ==  4) return("Bot Skill 3 Skin");
    	else if (ent.skin_selected ==  5) return("Imperial Stormtrooper");
    	else if (ent.skin_selected ==  6) return("Mandalorian Commando");
    	else if (ent.skin_selected ==  7) return("Executor");
    	else if (ent.skin_selected ==  8) return("Punisher");
    	else if (ent.skin_selected ==  9) return("Jules Winfield");
    	else if (ent.skin_selected == 10) return("Vincent Vega");
    	else if (ent.skin_selected == 11) return("Predator");
    	else if (ent.skin_selected == 12) return("Klingon");
    	else if (ent.skin_selected == 13) return("Crow");
    	else if (ent.skin_selected == 14) return("Spawn");
    	else if (ent.skin_selected == 15) return("Terminator");
    	else if (ent.skin_selected == 16) return("Hulk");
    	else if (ent.skin_selected == 17) return("Cyclops");
    	else if (ent.skin_selected == 18) return("Cable");
    	else if (ent.skin_selected == 19) return("Ghostbuster");
    	else if (ent.skin_selected == 20) return("Ranger");
    	else if (ent.skin_selected == 21) return("BioSuit");
    	else if (ent.skin_selected == 22) return("Boosk");
    	else if (ent.skin_selected == 23) return("X-Wing Pilot");
    	else if (ent.skin_selected == 24) return("Doom Marine");
    	else if (ent.skin_selected == 25) return("MoD");
    	else if (ent.skin_selected == 26) return("Lobo");
    	else if (ent.skin_selected == 27) return("Toad");
    	else if (ent.skin_selected == 28) return("Duke Nukem");
    	else if (ent.skin_selected == 29) return("Wolf");
    	else if (ent.skin_selected == 30) return("Guybrush Threepwood");
    	else if (ent.skin_selected == 31) return("Star Trek: TNG");
    	return;
    };

    "Progs.src"

    skins.qc		// 1997-12-30 skin support by Maddes

    "Defs.qc"

    // 1997-12-30 skin support by Maddes  start
    //
    // weapons.qc / client.qc / oldone.qc / skins.qc
    //
    float IMPULSE_SKIN_NEXT = 200;			// setting impulses for switches
    float IMPULSE_SKIN_PREV = 201;

    float SKINS_MAX_SUPPORTED = 32;			// maximum number of skins the player.mdl can have

    .float skin_selected;				// extra variable for choosen skin to enable event skins (biosuit, pent, etc.)

    entity ShubKiller;				// player who killed Shub for displaying right colors and skin on finale intermission

    void(entity ent, float prev_or_next) ChangeSkin;
    void(entity ent) SetSkin;
    string(entity ent) GetSkinName;
    void(entity ent) DisplaySkinName;
    // 1997-12-30 skin support by Maddes  end

Now you have to code the two impulses for selecting the next or previous skin. Also when a player dies his corpse should keep the skin and finally his skin selection should be remembered on respawn and level changes, just like with the auto-aim toggle.

    "Weapons.qc"

    void() ImpulseCommands =
    {
    ...
    	if (self.impulse == 12)
    		CycleWeaponReverseCommand ();

    // 1997-12-30 skin support by Maddes  start
    	if (self.impulse == IMPULSE_SKIN_NEXT)	// Impulse to select next skin
    	{
    		ChangeSkin(self, 1);
    	}
    	if (self.impulse == IMPULSE_SKIN_PREV)	// Impulse to select previous skin
    	{
    		ChangeSkin(self, -1);
    	}
    // 1997-12-30 skin support by Maddes  end

    	if (self.impulse == 255)
    		QuadCheat ();

    	self.impulse = 0;
    };

    "World.qc"

    void(entity ent) CopyToBodyQue =
    {
    	bodyque_head.skin = ent.skin;			// 1997-12-30 skin support by Maddes  copy the skin on the dead player
    ...
    };

    "Client.qc"

    void() SetChangeParms =
    {
    ...
    	parm9 = self.armortype * 100;

    	parm15 = self.skin_selected;		// 1997-12-30 skin support by Maddes  save skin on level changes
    };

    void() SetNewParms =
    {
    ...
    	parm9 = 0;

    	// 1997-12-30 skin support by Maddes  self.skin_selected is only saved in parm15 on respawn (see there)
    };

    void() DecodeLevelParms =
    {
    ...
    	self.armortype = parm9 * 0.01;

    // 1997-12-30 skin support by Maddes  start
    	self.skin_selected = parm15;		// remember skin after respawn and level changes
    	parm15 = 0;				// has to be cleared, because new clients get a copy of the "Parm..." variables from another client
    						// and all clients should start with skin #0
    // 1997-12-30 skin support by Maddes  end
    };
    ...
    void() respawn =
    {
    	if (coop)
    	{
    ...
    		// get the spawn parms as they were at level start
    		setspawnparms (self);
    		parm15 = self.skin_selected;		// 1997-12-30 skin support by Maddes  save skin on respawn
    		// respawn
    		PutClientInServer ();
    	}
    	else if (deathmatch)
    	{
    ...
    		// set default spawn parms
    		SetNewParms ();
    		parm15 = self.skin_selected;		// 1997-12-30 skin support by Maddes  save skin on respawn
    		// respawn
    		PutClientInServer ();
    	}
    	else
    	{	// restart the entire server
    		localcmd ("restart\n");
    	}

    	self.spawn_info = time + 0.1;			// 1997-12-24 Message on respawn by Maddes  wait a game tick
    };
    ...
    void() ClientKill =
    {
    ...
    	self.modelindex = modelindex_player;

    	self.skin = self.skin_selected;		// 1997-12-30 skin support by Maddes
    ...
    };
    ...
    void() PutClientInServer =
    {
    ...
    // oh, this is a hack!
    	self.skin = 0;			// 1997-12-30 skin support by Maddes
    	setmodel (self, "progs/eyes.mdl");
    	modelindex_eyes = self.modelindex;

    	setmodel (self, "progs/player.mdl");
    	modelindex_player = self.modelindex;
    	self.skin = self.skin_selected;		// 1997-12-30 skin support by Maddes
    ...
    };
    ...
    void() CheckPowerups =
    {
    	if (self.health <= 0)
    		return;

    // invisibility
    	if (self.invisible_finished)
    	{
    	...
    		// use the eyes
    		self.frame = 0;
    		SetSkin(self);					// 1997-12-30 skin support by Maddes
    		self.modelindex = modelindex_eyes;
    	}
    	else
    	{
    		self.modelindex = modelindex_player;	// don't use eyes
    		SetSkin(self);				// 1997-12-30 skin support by Maddes
    	}
    };
    ...
    void() PlayerPostThink =
    {
    ...
    // 1997-12-24 Message on respawn by Maddes  start
    	if (self.spawn_info)			// time set?
    	{
    		if (self.spawn_info <= time)	// print after time is reached
    		{
    			self.spawn_info = 0;	// clear time

    			local string text;

    			// here come all the print functions

    		// 1997-12-30 skin support by Maddes  start
    			sprint(self,"\"impulse ");
    			text = ftos(IMPULSE_SKIN_NEXT);
    			sprint(self,text);
    			sprint(self,"/");
    			text = ftos(IMPULSE_SKIN_PREV);
    			sprint(self,text);
    			sprint(self,"\" for next/prev skin\n");
    		// 1997-12-30 skin support by Maddes  end

    			DisplaySkinName(self);	// 1997-12-30 skin support by Maddes
    		}
    	}
    // 1997-12-24 Message on respawn by Maddes  end
    };
    ...
    void(entity targ, entity attacker) ClientObituary =
    {
    	local	float rnum;
    	local	string deathstring, deathstring2;
    	rnum = random();

    // 1997-12-30 skin support by Maddes/Zhenga  start
    	if (targ.classname == "monster_oldone")
    	{
    		if (attacker.owner)
    			ShubKiller = attacker.owner;
    	}
    // 1997-12-30 skin support by Maddes/Zhenga  end
    ...
    };

    "Oldone.qc"

    void() finale_4 =
    {
    ...
    // put a player model down
    	n = spawn();
    	setmodel (n, "progs/player.mdl");
    // 1997-12-30 skin support by Maddes/Zhenga  start
    	if (ShubKiller)
    	{
    		n.skin = ShubKiller.skin_selected;
    		n.colormap = ShubKiller.colormap;
    	}
    // 1997-12-30 skin support by Maddes/Zhenga  end
    	oldo = oldo - '32 264 0';
    	setorigin (n, oldo);
    	n.angles = '0 290 0';
    	n.frame = 1;
    ...
    };

    "Player.qc"

    void() PlayerDie =
    {
    ...
    	self.modelindex = modelindex_player;	// don't use eyes
    	self.skin = self.skin_selected;		// 1997-12-30 skin support by Maddes

    	if (deathmatch || coop)
    		DropBackpack();
    ...
    };

To provide event handling for skins, here for the biosuit, you have to define which skins are event-only and recognize them in the selection and display functions. Also you should set the players skin back to his selection, when he dies.
This will do the job:

    "Defs.qc"

    // 1998-01-04 event skin support by Maddes  start
    //
    // skins.qc
    //
    float SKINS_BIOSUIT = 21;			// index of biosuit skin (number - 1)
    // 1998-01-04 event skin support by Maddes  end

    "Skins.qc"

    void(entity ent, float prev_or_next) ChangeSkin =
    {
    ...
    	while ( (skin_new < 0)
    		|| (skin_new == SKINS_BIOSUIT)		// 1998-01-04 event skin support by Maddes  recognize biosuit
    		|| (skin_new >= SKINS_MAX_SUPPORTED) )
    	{
    		if (skin_new < 0)				// Cant be below zero (normally Quake Guy)
    		{
    ...
    		}
    		else if (skin_new >= SKINS_MAX_SUPPORTED)	// Cant be equal to maximum skins
    		{
    			skin_new = SKINS_MAX_SUPPORTED - 1;
    			prev_or_next = -1;			// change direction

    			number = SKINS_MAX_SUPPORTED;
    // 1998-01-04 event skin support by Maddes  start  correct number
    			if (number > SKINS_BIOSUIT)		// biosuit
    			{
    				number = number - 1;
    			}
    // 1998-01-04 event skin support by Maddes  end  correct number
    			text = ftos(number);			// Convert float to string for printing number
    			sprint(ent,"Last skin is #");		// Print "Last skin is #<number>" plus new-line
    			sprint(ent,text);
    			sprint(ent,"\n");
    		}
    		else
    		{
    			skin_new = skin_new + prev_or_next;	// Add or Subtract the skin-number
    		}
    	}
    ...
    };

    void(entity ent) SetSkin =
    {
    	if (ent.invisible_finished)		// eyes model does not need multiple skins
    		ent.skin = 0;
    // 1998-01-04 event skin support by Maddes  start
    	else if (ent.radsuit_finished)		// biosuit
    		ent.skin = SKINS_BIOSUIT;
    // 1998-01-04 event skin support by Maddes  end
    	else
    		ent.skin = ent.skin_selected;
    };
    ...
    void(entity ent) DisplaySkinName =
    {
    	local float number;			// For calculating issues
    	local string text;			// For printing issues

    	if (ent.classname != "player")
    		return;

    	number = ent.skin_selected + 1;		// skin number

    // 1998-01-04 event skin support by Maddes  start  correct number
    	if (number > SKINS_BIOSUIT)
    	{
    		number = number - 1;
    	}
    // 1998-01-04 event skin support by Maddes  end  correct number
    ...
    };

    "Client.qc"

    void() CheckPowerups =
    {
    ...
    // suit
    	if (self.radsuit_finished)
    	{
    ...
    		if (self.radsuit_finished < time)
    		{	// just stopped
    			self.items = self.items - IT_SUIT;
    			self.rad_time = 0;
    			self.radsuit_finished = 0;
    			SetSkin(self);				// 1998-01-04 event skin support by Maddes
    		}
    	}

    };

    "Items.qc"

    void() powerup_touch =
    {
    ...
    // do the apropriate action
    	if (self.classname == "item_artifact_envirosuit")
    	{
    		other.rad_time = 1;
    		other.radsuit_finished = time + 30;
    		SetSkin(other);		// 1998-01-04 event skin support by Maddes  set biosuit skin
    	}
    ...
    };

All SVC commands by Yun Zheng "Zhenga" Hu
Not all SVC commands are defined by default, although some of them can be very useful.
Just add them to your source:

    "Defs.qc"

    ...
    // protocol bytes
    // 1998-08-08 Complete SVC list by Zhenga  start
    float	SVC_BAD			= 0;
    float	SVC_NOP			= 1;
    float	SVC_DISCONNECT		= 2;
    float	SVC_UPDATESTAT		= 3;
    float	SVC_VERSION		= 4;
    float	SVC_SETVIEW		= 5;
    float	SVC_SOUND		= 6;
    float	SVC_TIME		= 7;
    float	SVC_PRINT		= 8;
    float	SVC_STUFFTEXT		= 9;
    float	SVC_SETANGLE		= 10;
    float	SVC_SERVERINFO		= 11;
    float	SVC_LIGHTSTYLE		= 12;
    float	SVC_UPDATENAME		= 13;
    float	SVC_UPDATEFRAGS		= 14;
    float	SVC_CLIENTDATA		= 15;
    float	SVC_STOPSOUND		= 16;
    float	SVC_UPDATECOLORS	= 17;
    float	SVC_PARTICLE		= 18;
    float	SVC_DAMAGE		= 19;
    float	SVC_SPAWNSTATIC		= 20;
    float	SVC_SPAWNBINARY		= 21;
    float	SVC_SPAWNBASELINE	= 22;
    // 1998-08-08 Complete SVC list by Zhenga  end
    float	SVC_TEMPENTITY		= 23;
    // 1998-08-08 Complete SVC list by Zhenga  start
    float	SVC_SETPAUSE		= 24;
    float	SVC_SIGNONNUM		= 25;
    float	SVC_CENTERPRINT		= 26;
    // 1998-08-08 Complete SVC list by Zhenga  end
    float	SVC_KILLEDMONSTER	= 27;
    float	SVC_FOUNDSECRET		= 28;
    float	SVC_SPAWNSTATICSOUND	= 29;	// 1998-08-08 Complete SVC list by Zhenga
    float	SVC_INTERMISSION	= 30;
    float	SVC_FINALE		= 31;
    float	SVC_CDTRACK		= 32;
    float	SVC_SELLSCREEN		= 33;
    float	SVC_CUTSCENE		= 34;	// 1998-08-08 Complete SVC list by Zhenga
    ...

Centerprint with multiple strings by Yun Zheng "Zhenga" Hu
It is possible to centerprint multiple strings at once, you just have to define some new centerprint functions.
The maximum number of strings is 7 with the original QCC, some other precompilers can only compile 6 or less.
The maximum number of characters in one string or multiple strings printed at once is around 2000.
The maximum number of characters per screen line is 40, independent from the current resolution.
The maximum number of lines per screen is 13, as more will cause weird problems in 320x200.
You can not print more than one variable at a time, or you have to rebuild each centerprint with a lot of WriteBytes.

    "Defs.qc"

    ...
    void(entity client, string s) centerprint 	= #73;		// sprint, but in middle
    // 1998-08-08 Centerprint with multiple strings by Zhenga  start
    void(entity client, string s, string s) centerprint2 = #73;
    void(entity client, string s, string s, string s) centerprint3 = #73;
    void(entity client, string s, string s, string s, string s) centerprint4 = #73;
    void(entity client, string s, string s, string s, string s, string s) centerprint5 = #73;
    void(entity client, string s, string s, string s, string s, string s, string s) centerprint6 = #73;
    void(entity client, string s, string s, string s, string s, string s, string s, string s) centerprint7 = #73;
    // 1998-08-08 Centerprint with multiple strings by Zhenga  end
    ...

An additonal workaround by Dirk "VoodooBug" Gerrits:
This is an interesting workaround, although it is not recommended as the compiler can not type check the parameters. And MEQCC does not like it either.

    ...
    // 1999-03-08 Centerprint with any number of strings by VoodooBug  start
    //void(entity client, string s) centerprint 	= #73;		// sprint, but in middle
    void(...) centerprint  = #73;  // sprint, but in middle
    // 1999-03-08 Centerprint with any number of strings by VoodooBug  end
    ...

Player drops backpack on suicide
This avoids one reason why some very "cool" guys kill themselves before being fragged.
Instead of respawning immediately, just use the normal death functions. Set the player's health to zero (died for the engine) and call the "Killed()" function.
As this also uses the "ClientObituary" function we have to define a new deathtype for commiting suicide.

    "Client.qc"

    void() ClientKill =
    {
    // 1998-07-27 Suicide during intermission fix by Zhenga  start
    	if ((intermission_running)&&((coop)||(deathmatch)))  // not allowed during intermission
    		return;
    // 1998-07-27 Suicide during intermission fix by Zhenga  end

    // 1998-08-10 Player drops backpack on suicide by Maddes  start
    /*
    	bprint (self.netname);
    	bprint (" suicides\n");
    	set_suicide_frame ();

    	self.modelindex = modelindex_player;

    	self.skin = self.skin_selected;		// 1997-12-30 skin support by Maddes

    	self.frags = self.frags - 2;	// extra penalty
    	respawn ();
    */
    	self.deathtype = "suicide";
    	self.health = 0;
    	Killed(self,self);
    // 1998-08-10 Player drops backpack on suicide by Maddes  end
    };
    ...
    void(entity targ, entity attacker) ClientObituary =
    {
    ...
    			if (targ == attacker)
    			{
    				// killed self
    				attacker.frags = attacker.frags - 1;
    				bprint (targ.netname);

    // 1998-08-10 Player drops backpack on suicide by Maddes  start
    				if (targ.deathtype == "suicide")
    				{
    					attacker.frags = attacker.frags - 1;	// extra penalty
    					bprint (" suicides\n");
    					return;
    				}
    // 1998-08-10 Player drops backpack on suicide by Maddes  end
    ...
    };

Smooth plat movement by Matt Barnett
Plats only move at 10 frames per seconds, which is very choppy when playing with 30fps.
Just decrease the time between movements to 0.01, so it can move at 100 frames per seconds making it much smoother.

    "Plats.qc"

    void() train_wait =
    {
    	if (self.wait)
    	{
    		self.nextthink = self.ltime + self.wait;
    		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
    	}
    	else
    // 1998-08-13 Smoother plat movement by Matt Barnett  start
    //		self.nextthink = self.ltime + 0.1;
    		self.nextthink = self.ltime + 0.01;
    // 1998-08-13 Smoother plat movement by Matt Barnett  end

    	self.think = train_next;
    };
    ...
    void() func_train_find =
    {
    	local entity	targ;

    	targ = find (world, targetname, self.target);
    	self.target = targ.target;
    	setorigin (self, targ.origin - self.mins);
    	if (!self.targetname)
    	{	// not triggered, so start immediately
    // 1998-08-13 Smoother plat movement by Matt Barnett  start
    //		self.nextthink = self.ltime + 0.1;
    		self.nextthink = self.ltime + 0.01;
    // 1998-08-13 Smoother plat movement by Matt Barnett  end
    		self.think = train_next;
    	}
    };

    void() func_train =
    {
    ...
    // start trains on the second frame, to make sure their targets have had
    // a chance to spawn
    // 1998-08-13 Smoother plat movement by Matt Barnett  start
    //	self.nextthink = self.ltime + 0.1;
    	self.nextthink = self.ltime + 0.01;
    // 1998-08-13 Smoother plat movement by Matt Barnett  end
    	self.think = func_train_find;
    };

    void() misc_teleporttrain =
    {
    ...
    // start trains on the second frame, to make sure their targets have had
    // a chance to spawn
    // 1998-08-13 Smoother plat movement by Matt Barnett  start
    //	self.nextthink = self.ltime + 0.1;
    	self.nextthink = self.ltime + 0.01;
    // 1998-08-13 Smoother plat movement by Matt Barnett  end
    	self.think = func_train_find;
    };

Better performance in handling of powerups
In each frame the player or eye model (for the ring of shadows) is set, and also the glowing for quad or pentagram, which can consume some time. The following code just sets the corresponding model or glowing when a powerup is picked up or expires.
The modelindex varaibles will be used across some files, so we have to define them in "Defs.qc".
Setting the eye model or glowing will be done when picking up an item, only the running out is still done in the "CheckPowerup()" function.

    "Defs.qc"

    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    //
    // defs.qc / client.qc / items.qc / player.qc
    //
    float	modelindex_eyes, modelindex_player;
    // 1998-07-23 Better performance in handling of powerups by Maddes  end

    "Client.qc"

    ...
    //	float	modelindex_eyes, modelindex_player;	// 1998-07-23 Better performance in handling of powerups by Maddes
    ...
    void() CheckPowerups =
    {
    ...
    // invisibility
    	if (self.invisible_finished)
    	{
    ...
    		if (self.invisible_finished < time)
    		{	// just stopped
    			self.items = self.items - IT_INVISIBILITY;
    			self.invisible_finished = 0;
    			self.invisible_time = 0;
    			self.modelindex = modelindex_player;	// 1998-07-23 Better performance in handling of powerups by Maddes
    								// don't use eyes
    			SetSkin(self);				// 1997-12-30 skin support by Maddes
    		}
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    /*
    		// use the eyes
    		self.frame = 0;
    		SetSkin(self);					// 1997-12-30 skin support by Maddes
    		self.modelindex = modelindex_eyes;
    */
    // 1998-07-23 Better performance in handling of powerups by Maddes  end
    	}
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    /*
    	else
    	{
    		self.modelindex = modelindex_player;	// don't use eyes
    		SetSkin(self);				// 1997-12-30 skin support by Maddes
    	}
    */
    // 1998-07-23 Better performance in handling of powerups by Maddes  end

    // invincibility
    	if (self.invincible_finished)
    	{
    ...
    		if (self.invincible_finished < time)
    		{	// just stopped
    			self.items = self.items - IT_INVULNERABILITY;
    			self.invincible_time = 0;
    			self.invincible_finished = 0;
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    			if (!self.super_damage_finished)
    				self.effects = self.effects - (self.effects & EF_DIMLIGHT);
    // 1998-07-23 Better performance in handling of powerups by Maddes  end
    		}
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    /*
    		if (self.invincible_finished > time)
    			self.effects = self.effects | EF_DIMLIGHT;
    		else
    			self.effects = self.effects - (self.effects & EF_DIMLIGHT);
    */
    // 1998-07-23 Better performance in handling of powerups by Maddes  end
    	}

    // super damage
    	if (self.super_damage_finished)
    	{
    ...
    		if (self.super_damage_finished < time)
    		{	// just stopped
    			self.items = self.items - IT_QUAD;
    			self.super_damage_finished = 0;
    			self.super_time = 0;
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    			if (!self.invincible_finished)
    				self.effects = self.effects - (self.effects & EF_DIMLIGHT);
    // 1998-07-23 Better performance in handling of powerups by Maddes  end
    		}
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    /*
    		if (self.super_damage_finished > time)
    			self.effects = self.effects | EF_DIMLIGHT;
    		else
    			self.effects = self.effects - (self.effects & EF_DIMLIGHT);
    */
    // 1998-07-23 Better performance in handling of powerups by Maddes  end
    	}
    ...
    };

    "Items.qc"

    void() powerup_touch =
    {
    ...
    // do the apropriate action
    	if (self.classname == "item_artifact_envirosuit")
    	{
    		other.rad_time = 1;
    		other.radsuit_finished = time + 30;
    		SetSkin(other);		// 1998-01-04 event skin support by Maddes  set biosuit skin
    	}

    	if (self.classname == "item_artifact_invulnerability")
    	{
    		other.invincible_time = 1;
    		other.invincible_finished = time + 30;
    		other.effects = other.effects | EF_DIMLIGHT;	// 1998-07-23 Better performance in handling of powerups by Maddes
    	}

    	if (self.classname == "item_artifact_invisibility")
    	{
    		other.invisible_time = 1;
    		other.invisible_finished = time + 30;
    		SetSkin(other);		// 1997-12-30 skin support by Maddes
    // 1998-07-23 Better performance in handling of powerups by Maddes  start
    		other.frame = 0;
    		other.modelindex = modelindex_eyes;
    // 1998-07-23 Better performance in handling of powerups by Maddes  end
    	}

    	if (self.classname == "item_artifact_super_damage")
    	{
    		other.super_time = 1;
    		other.super_damage_finished = time + 30;
    		other.effects = other.effects | EF_DIMLIGHT;	// 1998-07-23 Better performance in handling of powerups by Maddes
    	}

    	activator = other;
    	SUB_UseTargets();				// fire all targets / killtargets
    };

Drowning doesn't hurt armor
When a player is drowning his armor takes part of the damage. This is unrealistic or does it breathe too?
The following code will only hurt the player himself, but armor is still hurt by slime and lava. Be careful with this as some maps may become unplayable with it (long underwater runways, etc.).

    "Client.qc"

    void() WaterMove =
    {
    ...
    	if (self.waterlevel != 3)
    	{
    ...
    	}
    	else if (self.air_finished < time)
    	{	// drown!
    		if (self.pain_finished < time)
    		{
    			self.dmg = self.dmg + 2;
    			if (self.dmg > 15)
    				self.dmg = 10;
    			self.deathtype = "drowning";	// 1998-08-12 optional: Drowning doesn't hurt armor by Maddes/Athos
    			T_Damage (self, world, world, self.dmg);
    			self.pain_finished = time + 1;
    		}
    	}
    ...
    };

    "Combat.qc"

    void(entity targ, entity inflictor, entity attacker, float damage) T_Damage=
    {
    ...
    // save damage based on the target's armor level

    // 1998-08-12 Drowning doesn't hurt armor by Maddes/Athos  start
    	if (targ.deathtype != "drowning")
    	{
    // 1998-08-12 Drowning doesn't hurt armor by Maddes/Athos  end
    		save = ceil(targ.armortype*damage);
    		if (save >= targ.armorvalue)
    		{
    			save = targ.armorvalue;
    			targ.armortype = 0;	// lost all armor
    			targ.items = targ.items - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3));
    		}
    // 1998-08-12 Drowning doesn't hurt armor by Maddes/Athos  start
    	}
    	else
    		save = 0;
    // 1998-08-12 Drowning doesn't hurt armor by Maddes/Athos  end
    ...
    };

Improved weapon switching on pickups
The following code enhances the weapon switching when picking up weapons, ammo and backpacks.
If the player gets a new weapon, which is better than his current weapon, it will be switched to it just like before.
In all other cases weapon switching may occur only if a player was using his best weapon, and another weapon becomes his best weapon.

    "Items.qc"

    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //float(float w) RankForWeapon =
    float(entity ent, float w) RankForWeapon =
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    {
    	if (w == IT_LIGHTNING && ent.waterlevel <= 1)		// 1997-12-23 Thunderbolt fix by Maddes  recognize waterlevel
    		return 1;
    	if (w == IT_ROCKET_LAUNCHER)
    		return 2;
    	if (w == IT_SUPER_NAILGUN)
    		return 3;
    	if (w == IT_GRENADE_LAUNCHER)
    		return 4;
    	if (w == IT_SUPER_SHOTGUN)
    		return 5;
    	if (w == IT_NAILGUN)
    		return 6;
    	return 7;
    };

    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //void(float old, float new) Deathmatch_Weapon =
    void(entity ent, float new) Deathmatch_Weapon =
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    {
    	local float or, nr;

    // change self.weapon if desired
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //	or = RankForWeapon (self.weapon);
    //	nr = RankForWeapon (new);
    	or = RankForWeapon (ent, ent.weapon);
    	nr = RankForWeapon (ent, new);
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    	if ( nr < or )
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //		self.weapon = new;
    		ent.weapon = new;
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    };

    float() W_BestWeapon;

    void() weapon_touch =
    {
    //	local float	hadammo;	// 1998-08-15 Do not take unnecessary items but fire all targets by Maddes
    //	local float	old;		// 1998-08-15 Improved weapon switching on pickups by Maddes
    	local float	best, new;
    	local entity	stemp;
    	local float	leave;
    	local float	donttake;	// 1998-08-15 Do not take unnecessary items but fire all targets by Maddes
    ...
    	bound_other_ammo ();

    // change to the weapon
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //	old = other.items;
    // ...always if new one
    		if (!other.items & new)
    			Deathmatch_Weapon (other, new);
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    	other.items = other.items | new;

    	stemp = self;
    	self = other;

    // 1997-12-23 Thunderbolt fix by Maddes  start
    /* don't separate between SinglePlayer/Coop and Deathmatch
    	if (!deathmatch)
    		self.weapon = new;
    	else
    */
    // 1997-12-23 Thunderbolt fix by Maddes  end
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //		Deathmatch_Weapon (old, new);
    		if ( other.weapon == best )
    		{
    			self.weapon = W_BestWeapon();
    		}
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end

    	W_SetCurrentAmmo();
    ...
    };
    ...
    void() ammo_touch =
    {
    ...
    // change to a better weapon if appropriate
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    	stemp = self;
    	self = other;
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    	if ( other.weapon == best )
    	{
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //		stemp = self;
    //		self = other;
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    		self.weapon = W_BestWeapon();
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //		W_SetCurrentAmmo ();
    //		self = stemp;
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    	}

    // if changed current ammo, update it
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //	stemp = self;
    //	self = other;
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    	W_SetCurrentAmmo();
    ...
    };
    ...
    void() BackpackTouch =
    {
    	local string	s;
    	local float	best, new;
    //	local float	old;		// 1998-08-15 Improved weapon switching on pickups by Maddes
    	local entity	stemp;
    	local float	acount;
    ...
    // change weapons
    	other.ammo_shells = other.ammo_shells + self.ammo_shells;
    	other.ammo_nails = other.ammo_nails + self.ammo_nails;
    	other.ammo_rockets = other.ammo_rockets + self.ammo_rockets;
    	other.ammo_cells = other.ammo_cells + self.ammo_cells;

    	new = self.items;
    	if (!new)
    		new = other.weapon;
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //	old = other.items;
    // ...always if new one
    	if (!other.items & new)
    		Deathmatch_Weapon (other, new);
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end
    	other.items = other.items | new;

    	bound_other_ammo ();
    ...
    // remove the backpack, change self to the player
    	remove(self);
    	self = other;

    // change to the weapon
    // 1997-12-23 Thunderbolt fix by Maddes  start
    /* don't separate between SinglePlayer/Coop and Deathmatch
    	if (!deathmatch)
    		self.weapon = new;
    	else
    */
    // 1997-12-23 Thunderbolt fix by Maddes  end
    // 1998-08-15 Improved weapon switching on pickups by Maddes  start
    //		Deathmatch_Weapon (old, new);
    		if ( other.weapon == best )
    		{
    			self.weapon = W_BestWeapon();
    		}
    // 1998-08-15 Improved weapon switching on pickups by Maddes  end

    	W_SetCurrentAmmo ();
    };

a Quake Info Pool page
© 1997-2022 by Maddes