Quake DeveLS - Co-Op exit

Author: Chris Hilton
Difficulty: Medium

Weapons, weapons, weapons. What's with all the ultraviolence? Let's look forward and try something that might be useful for cooperative play.

If I had to guess, I would think Quake 2 coop play will be a lot like Quake, and what happened when someone hit the exit on Quake? Every player from the nether regions of the level got pulled forward to the next level. Nuh uh. Damn it, this time you're in a platoon and you will exit as such!

Here's what we'll do. When a player attempts to trigger the exit, we'll only allow it when all the players are near the exit. We're going to add a new console variable (cvar) called coopexit to control this behavior, sort of like a deathmatch flag. To add our new cvar, first we'll open up g_local.h and a few lines after line 463, like so ('+' signs indicate lines added).

 extern	cvar_t	*sv_cheats;
 extern	cvar_t	*maxclients;
+// CCH: New console variable
+extern	cvar_t	*coopexit;
 #define world	(&g_edicts[0])

g_local.h is included by all the .c files, so now we'll be able to use our new cvar anywhere. Edit g_main.c at line 45 adding the following lines and we'll actually define the cvar.
 cvar_t	*sv_cheats;
+// CCH: New console variable
+cvar_t	*coopexit;
 void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
 void ClientThink (edict_t *ent, usercmd_t *cmd);
 qboolean ClientConnect (edict_t *ent, char *userinfo, qboolean loadgame);

Okay, the variable is defined now, we really should initialize it. Open up g_save.c and add the following to InitGame() at line 163:
 	bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
 	bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+	// CCH: New console variable
+	coopexit = gi.cvar("coopexit", "0", CVAR_SERVERINFO|CVAR_LATCH);
 	// items
 	InitItems ();

This initializes our coopexit value to 0 (off) and sets the serverinfo and latch (save changes until server restart) flags, just like the deathmatch cvar.

Okay, we're all done with defining our new console variable, let's get to the gritty stuff. First we'll edit the use_target_changelevel() function in g_target.c at line 245 and add the following:

 void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
+	// CCH: A few local variables for coopexit
+	float	radius;
+	edict_t	*ent = NULL;
+	int		i;
+	vec3_t	v;
 	if (level.intermissiontime)
 		return;		// allready activated
+	// CCH: If coopexit is on, only exit when all players are at exit
+	if (coopexit->value)
+	{
+		radius = level.players * 100;
+		for (i=0 ; ivalue ; i++)
+		{
+			ent = g_edicts + 1 + i;
+			if (!ent->inuse || !ent->client)
+				continue;
+			VectorSubtract(activator->s.origin, ent->s.origin, v);
+			if (VectorLength(v) > radius)
+				return;
+		}
+	}
 	// if noexit, do a ton of damage to other
 	if (deathmatch->value && noexit->value && other != world)

If coopexit is set, we determine an 'exit radius' according to the number of players and then run through the players with the for loop. For the active players, we check their distance from the exit activator and, if we find one that's outside the radius, we return without letting them exit!

The only problem I've found with this is a slight bug with level.players. Basically, it doesn't keep track of the number of players too well. The way I've found to rectify this situation is to go to p_client.c at line 858 and remove the following line from the end of ClientConnect() ('-' signs indicate lines removed):

 	if (game.maxclients > 1)
 		gi.dprintf ("%s connected\n", ent->client->pers.netname);
-	level.players++;
 	return true;

and move it to line 739 in ClientBegin(), like so:
 	int		i;
+	// CCH: moved from ClientConnect() to here
+	level.players++;
 	if (deathmatch->value)
 		ClientBeginDeathmatch (ent);

This seems to get the right count most of the time, except for an occasional overcount bug I've reported to id. Oh, I almost forgot, you should always run with '+set zombietime -1000' to clear dead connections or you might get an overcount from that as well. Overcounts aren't so bad, they just extend the exit radius. Undercounts can be killer, though (like a 2 player game with an exit radius of 0). This shouldn't happen, but you can be sure by hardcoding radius to something like 500.

Hopefully, this will enhance your cooperative games and lead to more teamwork as you have to stick together. Full source and patch file at http://www.jump.net/~dctank.

Tutorial by Chris Hilton

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