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 ; i<maxclients->value ; 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.

Chris Hilton
chilton@scci-ad.com