Quake DeveLS - The Morgue

Author: SumFuka
Difficulty: Easy

Before we get to the real fun, I'd like to mention a tip that Christian Wilson gave to me. In the process of writing a Quake 2 mod, I find myself switching between the compiler and quake2 quite often. His tip is that you can actually leave quake OPEN whilst you recompile the gamex86.dll ! This saves lots of time. Here are the steps, and we will be using a 'game' directory here as well.

  1. Create a directory under C:\quake2, say C:\quake2\test
  2. Compile your gamex86 and copy it to C:\quake2\test
  3. Run C:\quake2\quake2.exe +set game "test" +map base1
  4. When you have finished testing in quake2, type disconnect on the console
  5. ALT-TAB back to your compiler and make your changes to the code
  6. Compile your gamex86 and copy it to C:\quake2\test
  7. ALT-TAB back to quake2 and type reconnect or map base1
  8. Goto step 4, repeat

Now that know how to compile a gamex86.dll successfully in our own quake2 game directory and modify one of the source files, lets do some serious more programming. Lets look at the ClientObituary function in p_client.c (line 70).

void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
What does this mean ? When you get killed, this function is called. Three variables (self, inflictor, attacker) are passed into the function, almost exactly like the old QuakeC days ! (ahhhhh... I get all nostalgic...)

These variables are of a certain type : "edict_t *"

edict_t * ???

Let's read "edict_t *" as "a pointer to a world entity". Ok, so "self" is a "pointer to a world entity". In other words, self is YOU (well, you died right ?) in the quake2 world. "inflictor" is a pointer to another entity in the world probably the rocket or bullet that finished you off. And "attacker" is also an entity... most probably a player entity (the dude who polished your ass). Get used to using "edict_t"... everything in quake2 is based on an edict_t !

How do we use this information ? Well all edict_t variables in quake2 have a classname. We can use this, for example, to find out HOW we were killed. Lets change lines 81-86 of p_client.c from this :

 
	if (attacker && attacker->client)
	{
		gi.bprintf (PRINT_MEDIUM,"%s was killed by %s\n", self->client->pers.netname, attacker->client->pers.netname);
		attacker->client->resp.score++;
		return;
	}
to this :
 
	if (attacker && attacker->client)
	{
		if (!strcmp(inflictor->classname, "grenade")) {
			// killed by grenade
			gi.bprintf (PRINT_MEDIUM,"%s plays 'catch the thermal detonator' with %s\n", self->client->pers.netname, attacker->client->pers.netname);
		} else if (!strncmp(3, inflictor->classname, "bfg")) {
			// killed by bfg
			gi.bprintf (PRINT_MEDIUM,"%s was made F.U.B.A.R by %s\n", self->client->pers.netname, attacker->client->pers.netname);
		} else if (!strcmp(inflictor->classname, "player")) {
			// killed by shotty
			gi.bprintf (PRINT_MEDIUM,"%s was blown away by %s\n", self->client->pers.netname, attacker->client->pers.netname);
		} else {
			// can't figure out how we were killed
			gi.bprintf (PRINT_MEDIUM,"%s was killed by %s\n", self->client->pers.netname, attacker->client->pers.netname);
		}
		attacker->client->resp.score++;
		return;
	}
String comparisons in C...

Ok string comparisons in c are done using the strcmp function. strcmp returns "false" (0) if the strings ARE the same, so we need to negate the result to determine when two strings ARE the same by using the NOT (!) operator. So !strcmp("steve","steve") returns "true" (1). And, !strcmp(inflictor->classname, "grenade") determines if the inflictor was a grenade.

Who killed me ? HOW ?...

So the code above simply looks at the classname of the inflictor and gives a different deathmessage based on that. Simple ! Unfortunately, the laser weapons and the rocketlauncher do not properly identify the inflictor (the inflictor classname is "noname"). This may be fixed by John Carmack, or maybe there is another attribute of the inflictor that we should be looking at ?

One way to tell if we were killed by the rl or hyperblaster is to look at attacker->client->pers.weapon->classname, but if someone fires a rocket at you and switches to another weapon, it will say you were killed by the weapon they are holding, not the weapon they fired. Oh well. I'll try and hack the weapons fire functions so that laser bolts and rockets are properly identified. Later maybe.

More messages !

If you're really keen try this out... first add two lines to the very start of your ClientObituary function... (you must put new local variables at the start of the function in C... this variable is "local" because we only need it for temporary use within this function)

 
void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
{
	double rnum;
	rnum = rand();
Now change the death messages we just changed before to look like this :

 
		if (!strcmp(inflictor->classname, "grenade")) {
			// killed by grenade
			if (rnum < 0.5)
				gi.bprintf (PRINT_MEDIUM,"%s plays 'catch the thermal detonator' with %s\n", self->client->pers.netname, attacker->client->pers.netname);
			else
				gi.bprintf (PRINT_MEDIUM,"%s ate %s's atomic apple.\n", self->client->pers.netname, attacker->client->pers.netname);
		} else if (!strncmp(inflictor->classname, "bfg", 3)) {
			// killed by bfg
			if (rnum < 0.33)
				gi.bprintf (PRINT_MEDIUM,"%s was made F.U.B.A.R by %s.\n", self->client->pers.netname, attacker->client->pers.netname);
			else if (rnum < 0.67)
				gi.bprintf (PRINT_MEDIUM,"%s bows to the almighty power of %s's BFG !\n", self->client->pers.netname, attacker->client->pers.netname);
			else 
				gi.bprintf (PRINT_MEDIUM,"%s is taught to fear the color of green by %s\n", self->client->pers.netname, attacker->client->pers.netname);
		} else if (!strcmp(inflictor->classname, "player")) {
			// killed by shotty
			if (rnum < 0.5)
				gi.bprintf (PRINT_MEDIUM,"%s was blown away by %s\n", self->client->pers.netname, attacker->client->pers.netname);
			else
				gi.bprintf (PRINT_MEDIUM,"%s eats %s's lead\n", self->client->pers.netname, attacker->client->pers.netname);
		} else {
			// can't figure out how we were killed
			if (rnum < 0.5)
				gi.bprintf (PRINT_MEDIUM,"%s was killed by %s's %s\n", self->client->pers.netname, attacker->client->pers.netname, inflictor->classname);
			else
				gi.bprintf (PRINT_MEDIUM,"%s was put out of their misery by %s\n", self->client->pers.netname, attacker->client->pers.netname);
		}
That's it ! Go and frag with your friends... the messages should be remotely more interesting...

STAY TUNED ! Next week.... WEAPONS ! (ah ah ah hah ah ahhhhh ! mmmm ! yummmy !)

Tutorial by SumFuka


This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4 or IE 3