Celebrating 12 years of Quake!

Untitled Document

to Booth Main

Health System Overhaul Tutorial

Okay, first things first, I assume you have already got yourself a
nice clean copy of the QuakeC source, have gotten yourself set up to compile
a progs.dat, and have at least a basic understanding of how the language
flows. If not, visit www.inside3d.com and forums.inside3d.com for more
info. (No, I'm not going to activate those links. You can cut and paste
like everybody else. Because I am lazy. =P )

Now, in the first of my qExpo tutorial series I'll show you how to
fix the multi-megahealth rot bug, AND overhaul the health system so that
you can add Strife style permanant maximum health increases as well as Doom
style health bonuses.

First, open up defs.qc and change line 689 from:

float (entity e, float healamount, float ignore)
T_Heal; // health function


void (entity e, float healamount, float ignore)
T_Heal; // health function

Now go to the bottom of the file and put:

.float rottime;

What we have just done is declare an entity assignable variable to
keep track of megahealth rot and fix a redeclaration problem later on.

Second, start by opening items.qc and going down to line number 102
which has T_Heal in it. Completely replace T_Heal with the following:

void (entity e, float healamount, float ignore)
T_Heal =


healamount = ceil(healamount);

e.health = e.health + healamount;

if ((!ignore) && (e.health >= e.max_health))

e.health = e.max_health;

if (e.health > rint(e.max_health*2))

e.health = rint(e.max_health*2);

// Megahealth = rot down the player's super

if (ignore == 1)


e.items = e.items | IT_SUPERHEALTH;

if(e.rottime < time)

e.rottime = time + 5;



Yes, I know. The original T_Heal was a float and not a void. What
this new T_Heal does however, is vastly superior to the old T_Heal. A look
now at the variables it accepts:

e is the targeted entity.

healamount is how much to heal you up.

ignore - set this to 0 for regular health packs, 1 for megahealth, and 2
for Doom style health bonuses. (Doom style health bonuses do not rot down
unless you grab a megahealth.)

Note that a whole buncha nasty, confusing math stuffs is going on
here. Also note that this function binds your maximum boosted health to
about double what your regular maxhealth is.

Anyhow, just keep in mind that e is the entity to target, time is
well, current game time, and rint and ceil are round integer to closest
whole and ceil is round up to whole.

Now, you can replace all of item_health with this: (Feel free to dispose
of item_megahealth_rot once you've finished comparing the code.)

/*QUAKED item_health (.3 .3 1) (0 0 0) (32 32
32) rotten megahealth

Health box. Normally gives 25 points.

Rotten box heals 5-10 points,

megahealth will add 100 health, then

rot you down to your maximum health limit,

one point per second.


float H_ROTTEN = 1;

float H_MEGA = 2;

.float healamount, healtype;

void() health_touch;

void() MHRot =


if(self.health <= self.max_health)


self.items = self.items - (other.items & IT_SUPERHEALTH);



self.health = self.health - 1;

self.rottime = time + 1;


void() item_health =


self.touch = health_touch;

if (self.spawnflags & H_ROTTEN)




setmodel(self, "maps/b_bh10.bsp");

self.noise = "items/r_item1.wav";

self.healamount = 15;

self.healtype = 0;



if (self.spawnflags & H_MEGA)




setmodel(self, "maps/b_bh100.bsp");

self.noise = "items/r_item2.wav";

self.healamount = 100;

self.healtype = 1;






setmodel(self, "maps/b_bh25.bsp");

self.noise = "items/health1.wav";

self.healamount = 30;

self.healtype = 0;


setsize (self, '0 0 0', '32 32 56');

StartItem ();



void() health_touch =


local float amount;

local string s;

if (other.classname != "player")


if(self.healtype == 0 && other.health >= other.max_health)


amount = self.healamount;

if (self.healtype > 0) // Megahealth?
Ignore max_health...


if (other.health >= rint(other.max_health*2))


T_Heal(other, amount, 1);



T_Heal(other, amount, 0);

sprint(other, "You receive ");

s = ftos(amount);

sprint(other, s);

sprint(other, " health\n");

// health touch sound

sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM);

stuffcmd (other, "bf\n");

self.model = string_null;

self.solid = SOLID_NOT;

if (deathmatch != 2) // deathmatch
2 is the silly old rules


if (deathmatch)

self.nextthink = time + 20;

self.think = SUB_regen;


activator = other;

SUB_UseTargets(); // fire all targets
/ killtargets


I want you to to look and compare what this new item_health and health_touch
to see what the differences are. Also note that MHRot(); does all the actual
megahealth rotting. ;)

We are almost there, open up client.qc and go to SetChangeParms();


if(self.health > 100) self.health = 100;


if(self.health > self.max_health) self.health
= self.max_health;

Add at the bottom of the function:

parm10 = self.max_health;

Add at the bottom of SetNewParms();

parm10 = 100;

Note that the above will reset your max health to 100 after dying
in deathmatch or upon return to start map...I leave figuring out how to
keep this from happening to you as a later exercise.

Continuing on, go to DecodeLevelParms();

Add at bottom of the function:

self.max_health = parm10;

Now go down to PlayerPostThink();

At the bottom add:

if((self.items & IT_SUPERHEALTH) &&
self.rottime <= time)



And we are done.

Save all your files, and compile.

By this point, you should have a completly revamped health system!

Now go in there, and add all those funky goodies like Strife maxhealth
increases and Doom Health bonuses! (Not to mention pick up megahealths
without fear of doublerot!)