This same method could be used to create ghost monsters -- monsters the player can run right through, but which his attacks still hit. It could also be used to allow team mates to move through walls that block enemies (team-friendly walls instead of team doors). This tutorial focuses on corpses.
Known Bugs:
1) (minor) If the player manages to get his attack origin is inside a corpse (despite their low height), his trace hit weapons like shotguns won't hit the corpse (or possibly other targets either). He just needs to step out of the corpse or use a different weapon, if he really cares about popping it. Corpses are so short this bug is hard to encounter (would need to walk into a corpse that's hanging over a ledge).
2) Traceline-based attacks that fire during player frames (i.e. player_light1, player_axe3, etc) won't hit corpses. The axe doesn't gib corpses, and the lightning bolt only hits them when the lightning starts (which happens in w_attack). This is because player frames occur between player post think and player pre think. I'm still looking into workarounds for this.
Summary
How's it work? We use a little-known feature of Quake: All of a player's physics code happens between the end of PlayerPreThink and the beginning of PlayerPostThink. Quake runs these steps in order on each player sequentially: My Prethink, my movement & physics, my postthink (other players and objects do not do their movement and physics during mine).
This means if something were solid most of the time, but magically unsolid between the pre and post think functions for a player, then the player could walk through the same things he could shoot. This also works on BSP objects like the func_wall, meaning you could make walls through which only players or only team members could pass in a teamplay mod like Team Fortress. I intend to make another tutorial of this later.
But on with the corpses tutorial!
In this tutorial I mark text that is changing as cyan. I may post a whole function, but only the cyan text is the part of interest. (to those of you who are colorblind, I apologize).
Step 1) Adding Definitions
Add these to the very bottom of defs.qc:
// Used for searching for objects that let players past but still block bullets.
// Set "semisolid" to "y" to let players walk through the entity.
.string semisolid;
// Used to store an entity's old solid value between PlayerPreThink and PlayerPostThink
.float oldSolid;
// Functions we use often
void() corpse_die;
void(entity ent, float hp, string headmdl) BecomeCorpse;
void(float hp, string headmdl) MonsterBecomeCorpse;
void(entity ent, float hp) CopyToBodyQue;
void(string gibname, float dm) ThrowGib;
void(string gibname, float dm) ThrowHead;
void() SemisolidPreThink;
void() SemisolidPostThink;
This adds two new fields to entities, semisolid and oldSolid. Semisolid is a way to mark monsters as something the player can walk right through. I use a string so that I can easily search through them all with the find() function. If you're using an engine that has the new find function that can use floats, then feel free to change how this field works (if you know what you're doing).
The oldSolid field stores an entity's normal solid value. Between PlayerPreThink and PlayerPostThink, the entity's solid will be set to SOLID_NOT. When then the entity's solid needs to be reset to the way it was, we have it in .oldSolid.
Step 2) Changing The World (or rather, world.qc)
There are several things we want to change in world.qc:
a) Add this to the worldspawn() function. Near the bottom is good. This line simply precaches the sound we're using for corpses popping (it's already in Quake, and just needs precaching).
// Corpses: corpse gib sound
precache_sound ("zombie/z_miss.wav");
b) Replace the CopyToBodyQue function with this:
// Turn this monster or bodyque into a corpse
void(entity ent, float hp) CopyToBodyQue =
{
bodyque_head.angles = ent.angles;
bodyque_head.model = ent.model;
bodyque_head.modelindex = ent.modelindex;
bodyque_head.frame = ent.frame;
bodyque_head.colormap = ent.colormap;
setorigin (bodyque_head, ent.origin + '0 0 1');
setsize(bodyque_head, ent.mins, ent.maxs);
BecomeCorpse(bodyque_head, hp, "progs/h_player.mdl");
bodyque_head.velocity = ent.velocity;
bodyque_head.flags = 0;
bodyque_head = bodyque_head.owner;
};
Make sure you get its changed parameters as well as the function body.

This function is normally used to leave a corpse behind when the player respawns. Player corpses were originally meant purely as decoration, and so they were nonsolid and trivial in standard Quake. However, this rendition makes them solid and poppable. Most of this happens in the BecomeCorpse function, which is next.
c) Add these functions below the CopyToBodyQue() function:
void(entity ent, float hp, string headmdl) BecomeCorpse =
{
// Gibbable Corpses: Turn me into a damagable corpse
ent.oldSolid = ent.solid = SOLID_SLIDEBOX;
ent.movetype = MOVETYPE_TOSS;
ent.takedamage = DAMAGE_AIM; // AIM makes grenades explode on impact; YES makes grenades bounce off (and prevents autoaim from targeting the corpse)
ent.semisolid = "y"; // let players walk through me
// Remember the model to use as the head when gibbed.
// BTW, if you call BecomeCorpse on a player this will change his name, so make sure to only call it on the bodyque instead.
// I arbitrarily picked .netname because it's unused by monsters and bodyques, but perhaps a different .string would be better.
ent.netname = headmdl;
// Make sure the corpse is short and won't block shots.
local vector min, max;
min = ent.mins;
max = ent.maxs;
// Set to however short you like the corpses. 30 keeps them from being so tall they block bullets, and from being so short monsters start walking on top of them.
max_z = min_z + 30;
setsize (ent, min, max);
// If a custom HP is specified, use that
if(hp > 0)
ent.health = hp;
else
ent.health = 30; // default
// If it was a monster, it's not a monster anymore (this is important to prevent crashes or corpses getting up as invincible monsters)
ent.flags = ent.flags - (ent.flags & FL_MONSTER);
ent.classname = "corpse";
// Corpses don't have a think.
// Don't repeat the last death frame for eternity (monsters do this all the time)
ent.think = SUB_Null;
ent.nextthink = -1;
// Gib when killed
ent.th_die = corpse_die;
// Clear other th_functions
ent.th_pain = SUB_Null; // Especially important: make sure it doesn't have pain frames that get it back up again! (invincible monsters bug)
ent.th_walk = SUB_Null;
ent.th_run = SUB_Null;
ent.th_stand = SUB_Null;
ent.th_missile = SUB_Null;
ent.th_melee = SUB_Null;
};
// Convenience funtion just redirects to BecomeCorpse, assuming self as ent.
void(float hp, string headmdl) MonsterBecomeCorpse =
{
BecomeCorpse(self, hp, headmdl);
};
void() corpse_die =
{
self.takedamage = DAMAGE_NO;
self.th_die = SUB_Null;
// Pick a default head model (unidentifiable flesh chunk) if one wasn't set
if(self.netname == "")
self.netname = "progs/gib3.mdl";
sound (self, CHAN_VOICE, "zombie/z_miss.wav", 1, ATTN_NORM);
// Use the head model we stored in BecomeCorpse
ThrowHead (self.netname, self.health);
// Then chuck a few more generic pieces of meat
ThrowGib ("progs/gib1.mdl", self.health);
ThrowGib ("progs/gib2.mdl", self.health);
ThrowGib ("progs/gib3.mdl", self.health);
};
void() SemisolidPreThink =
{
// Semisolid: Make all semisolid objects non-solid while this player runs his movement code
local entity e;
local string s;
s = "y";
e = find (world, semisolid, s);
while(e)
{
if(e != self)
{
e.oldSolid = e.solid;
if(e.solid == SOLID_BSP)
e.movetype = MOVETYPE_NONE;
e.solid = SOLID_NOT;
}
e = find (e, semisolid, s);
}
};
void() SemisolidPostThink =
{
// Semisolid: Make all semisolid objects (such as corpses) solid again, now that the player's move code is done.
local entity e;
local string s;
s = "y"; // self.semisolid;
e = find (world, semisolid, s);
while(e)
{
if(e != self && e.oldSolid != -1)
{
e.solid = e.oldSolid;
e.oldSolid = -1;
if(e.solid == SOLID_BSP)
e.movetype = MOVETYPE_PUSH;
}
e = find (e, semisolid, s);
}
};
MonsterBecomeCorpse() is a convenience function that simply calls BecomeCorpse with self as the entity to change. We'll be using this function in a later step.
The real work happens in BecomeCorpse. Since we're changing a monster or bodyque into a gibbable corpse, there's a few steps and precautions we have to take. Check the comments in the function for more info.
corpse_die() is simple enough: Play a sound, chuck some gibs, and make sure the corpse doesn't take damage anymore. You can use any sound you want (just make sure to precache it); I picked this one because it works so well for splattering bits.
SemisolidPreThink searches for all objects marked as semisolid (.semisolid == "y"), and makes them non-solid (after saving their solidity in .oldSolid). SemisolidPostThink reverses the process, restoring solid to oldSolid.
Fun Fact: Notice the BSP and MOVETYPE_PUSH checks? Yes, you can make BSP objects like func_wall semisolid. Players can pass through but monsters cannot (and with minor effort you can make team-friendly walls). I assume you could also do the same thing with doors and platforms, though you would want to take a special step to make their touch triggers semisolid as well.
d) OPTIONAL step: Increase the number of player corpses allowed
The bodyque is a linked loop data structure. Every time a player dies, it moves one of the bodyque entities to his position and that becomes his corpse. Then the player is then free to respawn and go off to live a fulfilling life of gleeful jumping and shooting for however long it takes him to die horribly again. The linked loop is only 4 entities long though, so corpses turn over quickly in a busy deathmatch game:
a -> b -> c -> d -> a
When all the corpses are used up in multiplayer, it just naturally loops around and starts recycling previous ones. Quake's bodyque only allows 4 corpses at a time by default to keep entity count low, which is good given that Quake ran on some pretty limited machines in its time. However, it's a little too sparse for our purposes. Now that corpses are more than mere decoration (but also something you can shoot), we might benefit from a higher corpse capacity.
NOTE that this ONLY affects player corpses (i.e. in deathmatch/coop/etc). Monsters simply become their own corpse, so you can automatically have as many corpses as you have monsters.
To increase the player corpse limit, we'll be replacing the initialization function that creates the bodyque linked loop. That function is horribly written, and adding more corpses the same way would be painful. I've completely rewritten the function to make it super-easy to change how many corpses are allowed.
Still in world.qc, replace the InitBodyQue function with this:
void() InitBodyQue =
{
local entity e, prev;
local float numBodies, num;
numBodies = 10; // how many player corpses to allow in the world at once
num = 0;
prev = world;
bodyque_head = world;
while(num < numBodies)
{
// Spawn another body
e = spawn();
e.classname = "bodyque";
// If this is the first, set the bodyque_head pointer (start of the list)
if(bodyque_head == world)
bodyque_head = e;
// Link up to previous item to form the linked list
if(prev != world)
e.owner = prev;
// Point prev to the new item
prev = e;
// Track how many we've made so we don't have an infinite loop
num = num + 1;
}
// Now that all corpse entities are created, turn the linked list into a linked loop
bodyque_head.owner = prev;
};
You can set numBodies to whatever you like. That just increases the maximum number of bodies allowed before it starts removing old ones. I picked 10 as a conservative, safe number, but it's up to you to pick the amount you want. Just keep in mind that it's more than entity count... if you choose a massive number, your multiplayer server might start filling up with gibbable corpses that players will have to mow through just to shoot each other.


Step 3: Making the player use the new functions:
a) PlayerPreThink
Add this line to the very bottom of the PlayerPreThink function:
SemisolidPreThink();
This calls the function that searches through all objects with a semisolid set to 'y', which is only corpses in this tutorial. For each such entity, it saves its solid in .oldSolid, then makes it non-solid.
b) PlayerPostThink
Add this line to the very top of the PlayerPostThink function:
void() PlayerPostThink =
{
SemisolidPostThink();
This puts the corpses back in their old solidity when a player finishes moving.
c) Update CopyToBodyQue calls
In client.qc, search for the two lines that call "CopyToBodyQue(self);" (they are close together). Add a second parameter so they both look like this:
CopyToBodyQue (self, 30);
Note that CopyToBodyQue now takes an HP value, so that you could pass anything you want as the corpse's health. 30 (the health of a monster_army) seems to be a good balance, in my opinion. You could choose to have the health affected by how hard the player was hit when killed, however (e.g. if he has less than -50 health, set the corpse to 1 HP). It's up to you.
Step 4: Making gibs disappear sooner (optional)
Since all these gibbable corpses could be spawning a lot of gibs, let's shorten the time they last.
Open player.qc and find the ThrowGib function. Sorten the nextthink time (how long until the gib removes itself), perhaps to "time + 4 + random()*4" (this is less than half the normal time). The new function looks like this:
void(string gibname, float dm) ThrowGib =
{
local entity new;
new = spawn();
new.origin = self.origin;
setmodel (new, gibname);
setsize (new, '0 0 0', '0 0 0');
new.velocity = VelocityForDamage (dm);
new.movetype = MOVETYPE_BOUNCE;
new.solid = SOLID_NOT;
new.avelocity_x = random()*600;
new.avelocity_y = random()*600;
new.avelocity_z = random()*600;
new.think = SUB_Remove;
new.ltime = time;
new.nextthink = time + 4 + random()*4;
new.frame = 0;
new.flags = 0;
};
Step 5: Making monsters gibbable
Making a monster gibbable is pretty easy, but you have to edit each monster file to do it. Let's start with the soldier.
Open soldier.qc, and scroll down to the death frame functions. We're interested in two of them: army_die10 and army_cdie11. These are the last frame in both death animations. Inside the two curly braces ( { and } ), add "MonsterBecomeCorpse(30, "progs/h_guard.mdl");". The animations will look like this now:
void() army_die1 =[ $death1, army_die2 ] {};
void() army_die2 =[ $death2, army_die3 ] {};
void() army_die3 =[ $death3, army_die4 ]
{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();};
void() army_die4 =[ $death4, army_die5 ] {};
void() army_die5 =[ $death5, army_die6 ] {};
void() army_die6 =[ $death6, army_die7 ] {};
void() army_die7 =[ $death7, army_die8 ] {};
void() army_die8 =[ $death8, army_die9 ] {};
void() army_die9 =[ $death9, army_die10 ] {};
void() army_die10 =[ $death10, army_die10 ] { MonsterBecomeCorpse(30, "progs/h_guard.mdl"); };
void() army_cdie1 =[ $deathc1, army_cdie2 ] {};
void() army_cdie2 =[ $deathc2, army_cdie3 ] {ai_back(5);};
void() army_cdie3 =[ $deathc3, army_cdie4 ]
{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();ai_back(4);};
void() army_cdie4 =[ $deathc4, army_cdie5 ] {ai_back(13);};
void() army_cdie5 =[ $deathc5, army_cdie6 ] {ai_back(3);};
void() army_cdie6 =[ $deathc6, army_cdie7 ] {ai_back(4);};
void() army_cdie7 =[ $deathc7, army_cdie8 ] {};
void() army_cdie8 =[ $deathc8, army_cdie9 ] {};
void() army_cdie9 =[ $deathc9, army_cdie10 ] {};
void() army_cdie10 =[ $deathc10, army_cdie11 ] {};
void() army_cdie11 =[ $deathc11, army_cdie11 ] { MonsterBecomeCorpse(30, "progs/h_guard.mdl"); };
You'll recognize MonsterBecomeCorpse as the function we added to world.qc earlier. 30 is the amount of HP the corpse has, and "progs/h_guard.mdl" is the model for the head to throw.
You can pick any value you want for corpse HP. You may decide soldier and dog corpses have only 1 HP and a single bullet will splatter them, but a shambler corpse has 300 HP and takes a lot of damage to splatter. It's up to you. If you pass in 0 as the HP, it will just use the default of 30 in CopyToBodyQue();
You can find the head model in each monster's normal death function, such as army_die(). It will be different for every monster, so make sure to change it in the calls to MonsterBecomeCorpse during your copy-pasting unless it's okay for shambler corpses to launch enforcer heads.

Now that soldiers are done, the next step is to repeat the process for the rest of the monsters you want to make gibbable. Tarbabies, chthon and shubby won't work so well with this (in fact, I strongly recommend against touching them with this tutorial), and zombies already require gibbing to die. Beyond those exceptions, however, the rest of the monsters are fair game. I recommend making the following monsters gibbable:
demon.qc
dog.qc
enforcer.qc
fish.qc (maybe)
hknight.qc
knight.qc
ogre.qc
shalrath.qc
shambler.qc
soldier.qc (done above)
wizard.qc
When all is said and done, you need only compile and test. Load up a Quake map (say, e1m1), and start blasting away at everything that moves. Then blast everything that doesn't move: corpses. Notice how you as a player can run right through the corpse uninhibited, something other corpse tutorials don't do.
What next?
From here, you could set self.semisolid = "y" on other entities you want players to be able to pass through, like a ghost monster.
Team games: Instead of setting .semisolid to "y", maybe in a team game you set it to the team name such as "red_team", and SemisolidPreThink and post think search for self.semisolid instead of "y". Then you could allow allies to move through each other (and allied walls/doors etc). I'll make a tutorial on this too, I think.
Enjoy!
Edits:
2011/04/22: Big update that makes monsters no longer use the bodyque (they just become their own corpse). Also fixed the bodyhopping bug (corpse shows stand frame for a brief moment), corpses falling through floors when the player stands in a body during its last death frame, and player corpses not working properly.
Also improved some explanations.
2011/04/13: Updated with a way to increase max corpses. Also updated bug list now that I have updated my engine (some of it was engine-related).
2011/04/11: Update to fix corpse size/height, so players can shoot past at other monsters without corpses blocking their bullets.
2011/04/01: Updated to launch correct head model from each monster's corpse. Also updated SemisolidPreThink & SemisolidPostThink to allow for BSP objects, for coders who want to take the next step on their own.