Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_ctf.c (102068B)


      1 /*
      2 Copyright (C) 1997-2001 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 #include "g_local.h"
     21 #include "m_player.h"
     22 
     23 typedef enum match_s {
     24 	MATCH_NONE,
     25 	MATCH_SETUP,
     26 	MATCH_PREGAME,
     27 	MATCH_GAME,
     28 	MATCH_POST
     29 } match_t;
     30 
     31 typedef enum {
     32 	ELECT_NONE,
     33 	ELECT_MATCH,
     34 	ELECT_ADMIN,
     35 	ELECT_MAP
     36 } elect_t;
     37 
     38 typedef struct ctfgame_s
     39 {
     40 	int team1, team2;
     41 	int total1, total2; // these are only set when going into intermission!
     42 	float last_flag_capture;
     43 	int last_capture_team;
     44 
     45 	match_t match;		// match state
     46 	float matchtime;	// time for match start/end (depends on state)
     47 	int lasttime;		// last time update
     48 
     49 	elect_t election;	// election type
     50 	edict_t *etarget;	// for admin election, who's being elected
     51 	char elevel[32];	// for map election, target level
     52 	int evotes;			// votes so far
     53 	int needvotes;		// votes needed
     54 	float electtime;	// remaining time until election times out
     55 	char emsg[256];		// election name
     56 
     57 
     58 	ghost_t ghosts[MAX_CLIENTS]; // ghost codes
     59 } ctfgame_t;
     60 
     61 ctfgame_t ctfgame;
     62 
     63 cvar_t *ctf;
     64 cvar_t *ctf_forcejoin;
     65 
     66 cvar_t *competition;
     67 cvar_t *matchlock;
     68 cvar_t *electpercentage;
     69 cvar_t *matchtime;
     70 cvar_t *matchsetuptime;
     71 cvar_t *matchstarttime;
     72 cvar_t *admin_password;
     73 cvar_t *warp_list;
     74 
     75 char *ctf_statusbar =
     76 "yb	-24 "
     77 
     78 // health
     79 "xv	0 "
     80 "hnum "
     81 "xv	50 "
     82 "pic 0 "
     83 
     84 // ammo
     85 "if 2 "
     86 "	xv	100 "
     87 "	anum "
     88 "	xv	150 "
     89 "	pic 2 "
     90 "endif "
     91 
     92 // armor
     93 "if 4 "
     94 "	xv	200 "
     95 "	rnum "
     96 "	xv	250 "
     97 "	pic 4 "
     98 "endif "
     99 
    100 // selected item
    101 "if 6 "
    102 "	xv	296 "
    103 "	pic 6 "
    104 "endif "
    105 
    106 "yb	-50 "
    107 
    108 // picked up item
    109 "if 7 "
    110 "	xv	0 "
    111 "	pic 7 "
    112 "	xv	26 "
    113 "	yb	-42 "
    114 "	stat_string 8 "
    115 "	yb	-50 "
    116 "endif "
    117 
    118 // timer
    119 "if 9 "
    120   "xv 246 "
    121   "num 2 10 "
    122   "xv 296 "
    123   "pic 9 "
    124 "endif "
    125 
    126 //  help / weapon icon 
    127 "if 11 "
    128   "xv 148 "
    129   "pic 11 "
    130 "endif "
    131 
    132 //  frags
    133 "xr	-50 "
    134 "yt 2 "
    135 "num 3 14 "
    136 
    137 //tech
    138 "yb -129 "
    139 "if 26 "
    140   "xr -26 "
    141   "pic 26 "
    142 "endif "
    143 
    144 // red team
    145 "yb -102 "
    146 "if 17 "
    147   "xr -26 "
    148   "pic 17 "
    149 "endif "
    150 "xr -62 "
    151 "num 2 18 "
    152 //joined overlay
    153 "if 22 "
    154   "yb -104 "
    155   "xr -28 "
    156   "pic 22 "
    157 "endif "
    158 
    159 // blue team
    160 "yb -75 "
    161 "if 19 "
    162   "xr -26 "
    163   "pic 19 "
    164 "endif "
    165 "xr -62 "
    166 "num 2 20 "
    167 "if 23 "
    168   "yb -77 "
    169   "xr -28 "
    170   "pic 23 "
    171 "endif "
    172 
    173 // have flag graph
    174 "if 21 "
    175   "yt 26 "
    176   "xr -24 "
    177   "pic 21 "
    178 "endif "
    179 
    180 // id view state
    181 "if 27 "
    182   "xv 0 "
    183   "yb -58 "
    184   "string \"Viewing\" "
    185   "xv 64 "
    186   "stat_string 27 "
    187 "endif "
    188 
    189 "if 28 "
    190   "xl 0 "
    191   "yb -78 "
    192   "stat_string 28 "
    193 "endif "
    194 ;
    195 
    196 static char *tnames[] = {
    197 	"item_tech1", "item_tech2", "item_tech3", "item_tech4",
    198 	NULL
    199 };
    200 
    201 void stuffcmd(edict_t *ent, char *s) 	
    202 {
    203    	gi.WriteByte (11);	        
    204 	gi.WriteString (s);
    205     gi.unicast (ent, true);	
    206 }
    207 
    208 /*--------------------------------------------------------------------------*/
    209 
    210 /*
    211 =================
    212 findradius
    213 
    214 Returns entities that have origins within a spherical area
    215 
    216 findradius (origin, radius)
    217 =================
    218 */
    219 static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad)
    220 {
    221 	vec3_t	eorg;
    222 	int		j;
    223 
    224 	if (!from)
    225 		from = g_edicts;
    226 	else
    227 		from++;
    228 	for ( ; from < &g_edicts[globals.num_edicts]; from++)
    229 	{
    230 		if (!from->inuse)
    231 			continue;
    232 #if 0
    233 		if (from->solid == SOLID_NOT)
    234 			continue;
    235 #endif
    236 		for (j=0 ; j<3 ; j++)
    237 			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
    238 		if (VectorLength(eorg) > rad)
    239 			continue;
    240 		return from;
    241 	}
    242 
    243 	return NULL;
    244 }
    245 
    246 static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
    247 {
    248 	VectorAdd(org, mins, p[0]);
    249 	VectorCopy(p[0], p[1]);
    250 	p[1][0] -= mins[0];
    251 	VectorCopy(p[0], p[2]);
    252 	p[2][1] -= mins[1];
    253 	VectorCopy(p[0], p[3]);
    254 	p[3][0] -= mins[0];
    255 	p[3][1] -= mins[1];
    256 	VectorAdd(org, maxs, p[4]);
    257 	VectorCopy(p[4], p[5]);
    258 	p[5][0] -= maxs[0];
    259 	VectorCopy(p[0], p[6]);
    260 	p[6][1] -= maxs[1];
    261 	VectorCopy(p[0], p[7]);
    262 	p[7][0] -= maxs[0];
    263 	p[7][1] -= maxs[1];
    264 }
    265 
    266 static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor)
    267 {
    268 	trace_t	trace;
    269 	vec3_t	targpoints[8];
    270 	int i;
    271 	vec3_t viewpoint;
    272 
    273 // bmodels need special checking because their origin is 0,0,0
    274 	if (targ->movetype == MOVETYPE_PUSH)
    275 		return false; // bmodels not supported
    276 
    277 	loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
    278 	
    279 	VectorCopy(inflictor->s.origin, viewpoint);
    280 	viewpoint[2] += inflictor->viewheight;
    281 
    282 	for (i = 0; i < 8; i++) {
    283 		trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
    284 		if (trace.fraction == 1.0)
    285 			return true;
    286 	}
    287 
    288 	return false;
    289 }
    290 
    291 /*--------------------------------------------------------------------------*/
    292 
    293 static gitem_t *flag1_item;
    294 static gitem_t *flag2_item;
    295 
    296 void CTFSpawn(void)
    297 {
    298 	if (!flag1_item)
    299 		flag1_item = FindItemByClassname("item_flag_team1");
    300 	if (!flag2_item)
    301 		flag2_item = FindItemByClassname("item_flag_team2");
    302 	memset(&ctfgame, 0, sizeof(ctfgame));
    303 	CTFSetupTechSpawn();
    304 
    305 	if (competition->value > 1) {
    306 		ctfgame.match = MATCH_SETUP;
    307 		ctfgame.matchtime = level.time + matchsetuptime->value * 60;
    308 	}
    309 }
    310 
    311 void CTFInit(void)
    312 {
    313 	ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO);
    314 	ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0);
    315 	competition = gi.cvar("competition", "0", CVAR_SERVERINFO);
    316 	matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO);
    317 	electpercentage = gi.cvar("electpercentage", "66", 0);
    318 	matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO);
    319 	matchsetuptime = gi.cvar("matchsetuptime", "10", 0);
    320 	matchstarttime = gi.cvar("matchstarttime", "20", 0);
    321 	admin_password = gi.cvar("admin_password", "", 0);
    322 	warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", 0);
    323 }
    324 
    325 /*--------------------------------------------------------------------------*/
    326 
    327 char *CTFTeamName(int team)
    328 {
    329 	switch (team) {
    330 	case CTF_TEAM1:
    331 		return "RED";
    332 	case CTF_TEAM2:
    333 		return "BLUE";
    334 	}
    335 	return "UKNOWN";
    336 }
    337 
    338 char *CTFOtherTeamName(int team)
    339 {
    340 	switch (team) {
    341 	case CTF_TEAM1:
    342 		return "BLUE";
    343 	case CTF_TEAM2:
    344 		return "RED";
    345 	}
    346 	return "UKNOWN";
    347 }
    348 
    349 int CTFOtherTeam(int team)
    350 {
    351 	switch (team) {
    352 	case CTF_TEAM1:
    353 		return CTF_TEAM2;
    354 	case CTF_TEAM2:
    355 		return CTF_TEAM1;
    356 	}
    357 	return -1; // invalid value
    358 }
    359 
    360 /*--------------------------------------------------------------------------*/
    361 
    362 edict_t *SelectRandomDeathmatchSpawnPoint (void);
    363 edict_t *SelectFarthestDeathmatchSpawnPoint (void);
    364 float	PlayersRangeFromSpot (edict_t *spot);
    365 
    366 void CTFAssignSkin(edict_t *ent, char *s)
    367 {
    368 	int playernum = ent-g_edicts-1;
    369 	char *p;
    370 	char t[64];
    371 
    372 	Com_sprintf(t, sizeof(t), "%s", s);
    373 
    374 	if ((p = strrchr(t, '/')) != NULL)
    375 		p[1] = 0;
    376 	else
    377 		strcpy(t, "male/");
    378 
    379 	switch (ent->client->resp.ctf_team) {
    380 	case CTF_TEAM1:
    381 		gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s", 
    382 			ent->client->pers.netname, t, CTF_TEAM1_SKIN) );
    383 		break;
    384 	case CTF_TEAM2:
    385 		gi.configstring (CS_PLAYERSKINS+playernum,
    386 			va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) );
    387 		break;
    388 	default:
    389 		gi.configstring (CS_PLAYERSKINS+playernum, 
    390 			va("%s\\%s", ent->client->pers.netname, s) );
    391 		break;
    392 	}
    393 //	gi.cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname);
    394 }
    395 
    396 void CTFAssignTeam(gclient_t *who)
    397 {
    398 	edict_t		*player;
    399 	int i;
    400 	int team1count = 0, team2count = 0;
    401 
    402 	who->resp.ctf_state = 0;
    403 
    404 	if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) {
    405 		who->resp.ctf_team = CTF_NOTEAM;
    406 		return;
    407 	}
    408 
    409 	for (i = 1; i <= maxclients->value; i++) {
    410 		player = &g_edicts[i];
    411 
    412 		if (!player->inuse || player->client == who)
    413 			continue;
    414 
    415 		switch (player->client->resp.ctf_team) {
    416 		case CTF_TEAM1:
    417 			team1count++;
    418 			break;
    419 		case CTF_TEAM2:
    420 			team2count++;
    421 		}
    422 	}
    423 	if (team1count < team2count)
    424 		who->resp.ctf_team = CTF_TEAM1;
    425 	else if (team2count < team1count)
    426 		who->resp.ctf_team = CTF_TEAM2;
    427 	else if (rand() & 1)
    428 		who->resp.ctf_team = CTF_TEAM1;
    429 	else
    430 		who->resp.ctf_team = CTF_TEAM2;
    431 }
    432 
    433 /*
    434 ================
    435 SelectCTFSpawnPoint
    436 
    437 go to a ctf point, but NOT the two points closest
    438 to other players
    439 ================
    440 */
    441 edict_t *SelectCTFSpawnPoint (edict_t *ent)
    442 {
    443 	edict_t	*spot, *spot1, *spot2;
    444 	int		count = 0;
    445 	int		selection;
    446 	float	range, range1, range2;
    447 	char	*cname;
    448 
    449 	if (ent->client->resp.ctf_state)
    450 		if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
    451 			return SelectFarthestDeathmatchSpawnPoint ();
    452 		else
    453 			return SelectRandomDeathmatchSpawnPoint ();
    454 
    455 	ent->client->resp.ctf_state++;
    456 
    457 	switch (ent->client->resp.ctf_team) {
    458 	case CTF_TEAM1:
    459 		cname = "info_player_team1";
    460 		break;
    461 	case CTF_TEAM2:
    462 		cname = "info_player_team2";
    463 		break;
    464 	default:
    465 		return SelectRandomDeathmatchSpawnPoint();
    466 	}
    467 
    468 	spot = NULL;
    469 	range1 = range2 = 99999;
    470 	spot1 = spot2 = NULL;
    471 
    472 	while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL)
    473 	{
    474 		count++;
    475 		range = PlayersRangeFromSpot(spot);
    476 		if (range < range1)
    477 		{
    478 			range1 = range;
    479 			spot1 = spot;
    480 		}
    481 		else if (range < range2)
    482 		{
    483 			range2 = range;
    484 			spot2 = spot;
    485 		}
    486 	}
    487 
    488 	if (!count)
    489 		return SelectRandomDeathmatchSpawnPoint();
    490 
    491 	if (count <= 2)
    492 	{
    493 		spot1 = spot2 = NULL;
    494 	}
    495 	else
    496 		count -= 2;
    497 
    498 	selection = rand() % count;
    499 
    500 	spot = NULL;
    501 	do
    502 	{
    503 		spot = G_Find (spot, FOFS(classname), cname);
    504 		if (spot == spot1 || spot == spot2)
    505 			selection++;
    506 	} while(selection--);
    507 
    508 	return spot;
    509 }
    510 
    511 /*------------------------------------------------------------------------*/
    512 /*
    513 CTFFragBonuses
    514 
    515 Calculate the bonuses for flag defense, flag carrier defense, etc.
    516 Note that bonuses are not cumaltive.  You get one, they are in importance
    517 order.
    518 */
    519 void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker)
    520 {
    521 	int i;
    522 	edict_t *ent;
    523 	gitem_t *flag_item, *enemy_flag_item;
    524 	int otherteam;
    525 	edict_t *flag, *carrier;
    526 	char *c;
    527 	vec3_t v1, v2;
    528 
    529 	if (targ->client && attacker->client) {
    530 		if (attacker->client->resp.ghost)
    531 			if (attacker != targ)
    532 				attacker->client->resp.ghost->kills++;
    533 		if (targ->client->resp.ghost)
    534 			targ->client->resp.ghost->deaths++;
    535 	}
    536 
    537 	// no bonus for fragging yourself
    538 	if (!targ->client || !attacker->client || targ == attacker)
    539 		return;
    540 
    541 	otherteam = CTFOtherTeam(targ->client->resp.ctf_team);
    542 	if (otherteam < 0)
    543 		return; // whoever died isn't on a team
    544 
    545 	// same team, if the flag at base, check to he has the enemy flag
    546 	if (targ->client->resp.ctf_team == CTF_TEAM1) {
    547 		flag_item = flag1_item;
    548 		enemy_flag_item = flag2_item;
    549 	} else {
    550 		flag_item = flag2_item;
    551 		enemy_flag_item = flag1_item;
    552 	}
    553 
    554 	// did the attacker frag the flag carrier?
    555 	if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
    556 		attacker->client->resp.ctf_lastfraggedcarrier = level.time;
    557 		attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS;
    558 		gi.cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n",
    559 			CTF_FRAG_CARRIER_BONUS);
    560 
    561 		// the target had the flag, clear the hurt carrier
    562 		// field on the other team
    563 		for (i = 1; i <= maxclients->value; i++) {
    564 			ent = g_edicts + i;
    565 			if (ent->inuse && ent->client->resp.ctf_team == otherteam)
    566 				ent->client->resp.ctf_lasthurtcarrier = 0;
    567 		}
    568 		return;
    569 	}
    570 
    571 	if (targ->client->resp.ctf_lasthurtcarrier &&
    572 		level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT &&
    573 		!attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) {
    574 		// attacker is on the same team as the flag carrier and
    575 		// fragged a guy who hurt our flag carrier
    576 		attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS;
    577 		gi.bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n",
    578 			attacker->client->pers.netname, 
    579 			CTFTeamName(attacker->client->resp.ctf_team));
    580 		if (attacker->client->resp.ghost)
    581 			attacker->client->resp.ghost->carrierdef++;
    582 		return;
    583 	}
    584 
    585 	// flag and flag carrier area defense bonuses
    586 
    587 	// we have to find the flag and carrier entities
    588 
    589 	// find the flag
    590 	switch (attacker->client->resp.ctf_team) {
    591 	case CTF_TEAM1:
    592 		c = "item_flag_team1";
    593 		break;
    594 	case CTF_TEAM2:
    595 		c = "item_flag_team2";
    596 		break;
    597 	default:
    598 		return;
    599 	}
    600 
    601 	flag = NULL;
    602 	while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) {
    603 		if (!(flag->spawnflags & DROPPED_ITEM))
    604 			break;
    605 	}
    606 
    607 	if (!flag)
    608 		return; // can't find attacker's flag
    609 
    610 	// find attacker's team's flag carrier
    611 	for (i = 1; i <= maxclients->value; i++) {
    612 		carrier = g_edicts + i;
    613 		if (carrier->inuse && 
    614 			carrier->client->pers.inventory[ITEM_INDEX(flag_item)])
    615 			break;
    616 		carrier = NULL;
    617 	}
    618 
    619 	// ok we have the attackers flag and a pointer to the carrier
    620 
    621 	// check to see if we are defending the base's flag
    622 	VectorSubtract(targ->s.origin, flag->s.origin, v1);
    623 	VectorSubtract(attacker->s.origin, flag->s.origin, v2);
    624 
    625 	if ((VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS ||
    626 		VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS ||
    627 		loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) &&
    628 		attacker->client->resp.ctf_team != targ->client->resp.ctf_team) {
    629 		// we defended the base flag
    630 		attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS;
    631 		if (flag->solid == SOLID_NOT)
    632 			gi.bprintf(PRINT_MEDIUM, "%s defends the %s base.\n",
    633 				attacker->client->pers.netname, 
    634 				CTFTeamName(attacker->client->resp.ctf_team));
    635 		else
    636 			gi.bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n",
    637 				attacker->client->pers.netname, 
    638 				CTFTeamName(attacker->client->resp.ctf_team));
    639 		if (attacker->client->resp.ghost)
    640 			attacker->client->resp.ghost->basedef++;
    641 		return;
    642 	}
    643 
    644 	if (carrier && carrier != attacker) {
    645 		VectorSubtract(targ->s.origin, carrier->s.origin, v1);
    646 		VectorSubtract(attacker->s.origin, carrier->s.origin, v1);
    647 
    648 		if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS ||
    649 			VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS ||
    650 			loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) {
    651 			attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS;
    652 			gi.bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n",
    653 				attacker->client->pers.netname, 
    654 				CTFTeamName(attacker->client->resp.ctf_team));
    655 			if (attacker->client->resp.ghost)
    656 				attacker->client->resp.ghost->carrierdef++;
    657 			return;
    658 		}
    659 	}
    660 }
    661 
    662 void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker)
    663 {
    664 	gitem_t *flag_item;
    665 
    666 	if (!targ->client || !attacker->client)
    667 		return;
    668 
    669 	if (targ->client->resp.ctf_team == CTF_TEAM1)
    670 		flag_item = flag2_item;
    671 	else
    672 		flag_item = flag1_item;
    673 
    674 	if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] &&
    675 		targ->client->resp.ctf_team != attacker->client->resp.ctf_team)
    676 		attacker->client->resp.ctf_lasthurtcarrier = level.time;
    677 }
    678 
    679 
    680 /*------------------------------------------------------------------------*/
    681 
    682 void CTFResetFlag(int ctf_team)
    683 {
    684 	char *c;
    685 	edict_t *ent;
    686 
    687 	switch (ctf_team) {
    688 	case CTF_TEAM1:
    689 		c = "item_flag_team1";
    690 		break;
    691 	case CTF_TEAM2:
    692 		c = "item_flag_team2";
    693 		break;
    694 	default:
    695 		return;
    696 	}
    697 
    698 	ent = NULL;
    699 	while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) {
    700 		if (ent->spawnflags & DROPPED_ITEM)
    701 			G_FreeEdict(ent);
    702 		else {
    703 			ent->svflags &= ~SVF_NOCLIENT;
    704 			ent->solid = SOLID_TRIGGER;
    705 			gi.linkentity(ent);
    706 			ent->s.event = EV_ITEM_RESPAWN;
    707 		}
    708 	}
    709 }
    710 
    711 void CTFResetFlags(void)
    712 {
    713 	CTFResetFlag(CTF_TEAM1);
    714 	CTFResetFlag(CTF_TEAM2);
    715 }
    716 
    717 qboolean CTFPickup_Flag(edict_t *ent, edict_t *other)
    718 {
    719 	int ctf_team;
    720 	int i;
    721 	edict_t *player;
    722 	gitem_t *flag_item, *enemy_flag_item;
    723 
    724 	// figure out what team this flag is
    725 	if (strcmp(ent->classname, "item_flag_team1") == 0)
    726 		ctf_team = CTF_TEAM1;
    727 	else if (strcmp(ent->classname, "item_flag_team2") == 0)
    728 		ctf_team = CTF_TEAM2;
    729 	else {
    730 		gi.cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n");
    731 		return false;
    732 	}
    733 
    734 	// same team, if the flag at base, check to he has the enemy flag
    735 	if (ctf_team == CTF_TEAM1) {
    736 		flag_item = flag1_item;
    737 		enemy_flag_item = flag2_item;
    738 	} else {
    739 		flag_item = flag2_item;
    740 		enemy_flag_item = flag1_item;
    741 	}
    742 
    743 	if (ctf_team == other->client->resp.ctf_team) {
    744 
    745 		if (!(ent->spawnflags & DROPPED_ITEM)) {
    746 			// the flag is at home base.  if the player has the enemy
    747 			// flag, he's just won!
    748 		
    749 			if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
    750 				gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n",
    751 						other->client->pers.netname, CTFOtherTeamName(ctf_team));
    752 				other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0;
    753 
    754 				ctfgame.last_flag_capture = level.time;
    755 				ctfgame.last_capture_team = ctf_team;
    756 				if (ctf_team == CTF_TEAM1)
    757 					ctfgame.team1++;
    758 				else
    759 					ctfgame.team2++;
    760 
    761 				gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0);
    762 
    763 				// other gets another 10 frag bonus
    764 				other->client->resp.score += CTF_CAPTURE_BONUS;
    765 				if (other->client->resp.ghost)
    766 					other->client->resp.ghost->caps++;
    767 
    768 				// Ok, let's do the player loop, hand out the bonuses
    769 				for (i = 1; i <= maxclients->value; i++) {
    770 					player = &g_edicts[i];
    771 					if (!player->inuse)
    772 						continue;
    773 
    774 					if (player->client->resp.ctf_team != other->client->resp.ctf_team)
    775 						player->client->resp.ctf_lasthurtcarrier = -5;
    776 					else if (player->client->resp.ctf_team == other->client->resp.ctf_team) {
    777 						if (player != other)
    778 							player->client->resp.score += CTF_TEAM_BONUS;
    779 						// award extra points for capture assists
    780 						if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) {
    781 							gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname);
    782 							player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS;
    783 						}
    784 						if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) {
    785 							gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname);
    786 							player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS;
    787 						}
    788 					}
    789 				}
    790 
    791 				CTFResetFlags();
    792 				return false;
    793 			}
    794 			return false; // its at home base already
    795 		}	
    796 		// hey, its not home.  return it by teleporting it back
    797 		gi.bprintf(PRINT_HIGH, "%s returned the %s flag!\n", 
    798 			other->client->pers.netname, CTFTeamName(ctf_team));
    799 		other->client->resp.score += CTF_RECOVERY_BONUS;
    800 		other->client->resp.ctf_lastreturnedflag = level.time;
    801 		gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
    802 		//CTFResetFlag will remove this entity!  We must return false
    803 		CTFResetFlag(ctf_team);
    804 		return false;
    805 	}
    806 
    807 	// hey, its not our flag, pick it up
    808 	gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n",
    809 		other->client->pers.netname, CTFTeamName(ctf_team));
    810 	other->client->resp.score += CTF_FLAG_BONUS;
    811 
    812 	other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1;
    813 	other->client->resp.ctf_flagsince = level.time;
    814 
    815 	// pick up the flag
    816 	// if it's not a dropped flag, we just make is disappear
    817 	// if it's dropped, it will be removed by the pickup caller
    818 	if (!(ent->spawnflags & DROPPED_ITEM)) {
    819 		ent->flags |= FL_RESPAWN;
    820 		ent->svflags |= SVF_NOCLIENT;
    821 		ent->solid = SOLID_NOT;
    822 	}
    823 	return true;
    824 }
    825 
    826 static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
    827 {
    828 	//owner (who dropped us) can't touch for two secs
    829 	if (other == ent->owner && 
    830 		ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2)
    831 		return;
    832 
    833 	Touch_Item (ent, other, plane, surf);
    834 }
    835 
    836 static void CTFDropFlagThink(edict_t *ent)
    837 {
    838 	// auto return the flag
    839 	// reset flag will remove ourselves
    840 	if (strcmp(ent->classname, "item_flag_team1") == 0) {
    841 		CTFResetFlag(CTF_TEAM1);
    842 		gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
    843 			CTFTeamName(CTF_TEAM1));
    844 	} else if (strcmp(ent->classname, "item_flag_team2") == 0) {
    845 		CTFResetFlag(CTF_TEAM2);
    846 		gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
    847 			CTFTeamName(CTF_TEAM2));
    848 	}
    849 }
    850 
    851 // Called from PlayerDie, to drop the flag from a dying player
    852 void CTFDeadDropFlag(edict_t *self)
    853 {
    854 	edict_t *dropped = NULL;
    855 
    856 	if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
    857 		dropped = Drop_Item(self, flag1_item);
    858 		self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0;
    859 		gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
    860 			self->client->pers.netname, CTFTeamName(CTF_TEAM1));
    861 	} else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
    862 		dropped = Drop_Item(self, flag2_item);
    863 		self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0;
    864 		gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
    865 			self->client->pers.netname, CTFTeamName(CTF_TEAM2));
    866 	}
    867 
    868 	if (dropped) {
    869 		dropped->think = CTFDropFlagThink;
    870 		dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT;
    871 		dropped->touch = CTFDropFlagTouch;
    872 	}
    873 }
    874 
    875 qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item)
    876 {
    877 	if (rand() & 1) 
    878 		gi.cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n");
    879 	else
    880 		gi.cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n");
    881 	return false;
    882 }
    883 
    884 static void CTFFlagThink(edict_t *ent)
    885 {
    886 	if (ent->solid != SOLID_NOT)
    887 		ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);
    888 	ent->nextthink = level.time + FRAMETIME;
    889 }
    890 
    891 
    892 void CTFFlagSetup (edict_t *ent)
    893 {
    894 	trace_t		tr;
    895 	vec3_t		dest;
    896 	float		*v;
    897 
    898 	v = tv(-15,-15,-15);
    899 	VectorCopy (v, ent->mins);
    900 	v = tv(15,15,15);
    901 	VectorCopy (v, ent->maxs);
    902 
    903 	if (ent->model)
    904 		gi.setmodel (ent, ent->model);
    905 	else
    906 		gi.setmodel (ent, ent->item->world_model);
    907 	ent->solid = SOLID_TRIGGER;
    908 	ent->movetype = MOVETYPE_TOSS;  
    909 	ent->touch = Touch_Item;
    910 
    911 	v = tv(0,0,-128);
    912 	VectorAdd (ent->s.origin, v, dest);
    913 
    914 	tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
    915 	if (tr.startsolid)
    916 	{
    917 		gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
    918 		G_FreeEdict (ent);
    919 		return;
    920 	}
    921 
    922 	VectorCopy (tr.endpos, ent->s.origin);
    923 
    924 	gi.linkentity (ent);
    925 
    926 	ent->nextthink = level.time + FRAMETIME;
    927 	ent->think = CTFFlagThink;
    928 }
    929 
    930 void CTFEffects(edict_t *player)
    931 {
    932 	player->s.effects &= ~(EF_FLAG1 | EF_FLAG2);
    933 	if (player->health > 0) {
    934 		if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
    935 			player->s.effects |= EF_FLAG1;
    936 		}
    937 		if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
    938 			player->s.effects |= EF_FLAG2;
    939 		}
    940 	}
    941 
    942 	if (player->client->pers.inventory[ITEM_INDEX(flag1_item)])
    943 		player->s.modelindex3 = gi.modelindex("players/male/flag1.md2");
    944 	else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)])
    945 		player->s.modelindex3 = gi.modelindex("players/male/flag2.md2");
    946 	else
    947 		player->s.modelindex3 = 0;
    948 }
    949 
    950 // called when we enter the intermission
    951 void CTFCalcScores(void)
    952 {
    953 	int i;
    954 
    955 	ctfgame.total1 = ctfgame.total2 = 0;
    956 	for (i = 0; i < maxclients->value; i++) {
    957 		if (!g_edicts[i+1].inuse)
    958 			continue;
    959 		if (game.clients[i].resp.ctf_team == CTF_TEAM1)
    960 			ctfgame.total1 += game.clients[i].resp.score;
    961 		else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
    962 			ctfgame.total2 += game.clients[i].resp.score;
    963 	}
    964 }
    965 
    966 void CTFID_f (edict_t *ent)
    967 {
    968 	if (ent->client->resp.id_state) {
    969 		gi.cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n");
    970 		ent->client->resp.id_state = false;
    971 	} else {
    972 		gi.cprintf(ent, PRINT_HIGH, "Activating player identication display.\n");
    973 		ent->client->resp.id_state = true;
    974 	}
    975 }
    976 
    977 static void CTFSetIDView(edict_t *ent)
    978 {
    979 	vec3_t	forward, dir;
    980 	trace_t	tr;
    981 	edict_t	*who, *best;
    982 	float	bd = 0, d;
    983 	int i;
    984 
    985 	ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
    986 
    987 	AngleVectors(ent->client->v_angle, forward, NULL, NULL);
    988 	VectorScale(forward, 1024, forward);
    989 	VectorAdd(ent->s.origin, forward, forward);
    990 	tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
    991 	if (tr.fraction < 1 && tr.ent && tr.ent->client) {
    992 		ent->client->ps.stats[STAT_CTF_ID_VIEW] = 
    993 			CS_PLAYERSKINS + (ent - g_edicts - 1);
    994 		return;
    995 	}
    996 
    997 	AngleVectors(ent->client->v_angle, forward, NULL, NULL);
    998 	best = NULL;
    999 	for (i = 1; i <= maxclients->value; i++) {
   1000 		who = g_edicts + i;
   1001 		if (!who->inuse || who->solid == SOLID_NOT)
   1002 			continue;
   1003 		VectorSubtract(who->s.origin, ent->s.origin, dir);
   1004 		VectorNormalize(dir);
   1005 		d = DotProduct(forward, dir);
   1006 		if (d > bd && loc_CanSee(ent, who)) {
   1007 			bd = d;
   1008 			best = who;
   1009 		}
   1010 	}
   1011 	if (bd > 0.90)
   1012 		ent->client->ps.stats[STAT_CTF_ID_VIEW] = 
   1013 			CS_PLAYERSKINS + (best - g_edicts - 1);
   1014 }
   1015 
   1016 void SetCTFStats(edict_t *ent)
   1017 {
   1018 	gitem_t *tech;
   1019 	int i;
   1020 	int p1, p2;
   1021 	edict_t *e;
   1022 
   1023 	if (ctfgame.match > MATCH_NONE)
   1024 		ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH;
   1025 	else
   1026 		ent->client->ps.stats[STAT_CTF_MATCH] = 0;
   1027 
   1028 	//ghosting
   1029 	if (ent->client->resp.ghost) {
   1030 		ent->client->resp.ghost->score = ent->client->resp.score;
   1031 		strcpy(ent->client->resp.ghost->netname, ent->client->pers.netname);
   1032 		ent->client->resp.ghost->number = ent->s.number;
   1033 	}
   1034 
   1035 	// logo headers for the frag display
   1036 	ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = gi.imageindex ("ctfsb1");
   1037 	ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = gi.imageindex ("ctfsb2");
   1038 
   1039 	// if during intermission, we must blink the team header of the winning team
   1040 	if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second
   1041 		// note that ctfgame.total[12] is set when we go to intermission
   1042 		if (ctfgame.team1 > ctfgame.team2)
   1043 			ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
   1044 		else if (ctfgame.team2 > ctfgame.team1)
   1045 			ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
   1046 		else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
   1047 			ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
   1048 		else if (ctfgame.total2 > ctfgame.total1) 
   1049 			ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
   1050 		else { // tie game!
   1051 			ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
   1052 			ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
   1053 		}
   1054 	}
   1055 
   1056 	// tech icon
   1057 	i = 0;
   1058 	ent->client->ps.stats[STAT_CTF_TECH] = 0;
   1059 	while (tnames[i]) {
   1060 		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
   1061 			ent->client->pers.inventory[ITEM_INDEX(tech)]) {
   1062 			ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon);
   1063 			break;
   1064 		}
   1065 		i++;
   1066 	}
   1067 
   1068 	// figure out what icon to display for team logos
   1069 	// three states:
   1070 	//   flag at base
   1071 	//   flag taken
   1072 	//   flag dropped
   1073 	p1 = gi.imageindex ("i_ctf1");
   1074 	e = G_Find(NULL, FOFS(classname), "item_flag_team1");
   1075 	if (e != NULL) {
   1076 		if (e->solid == SOLID_NOT) {
   1077 			int i;
   1078 
   1079 			// not at base
   1080 			// check if on player
   1081 			p1 = gi.imageindex ("i_ctf1d"); // default to dropped
   1082 			for (i = 1; i <= maxclients->value; i++)
   1083 				if (g_edicts[i].inuse &&
   1084 					g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) {
   1085 					// enemy has it
   1086 					p1 = gi.imageindex ("i_ctf1t");
   1087 					break;
   1088 				}
   1089 		} else if (e->spawnflags & DROPPED_ITEM)
   1090 			p1 = gi.imageindex ("i_ctf1d"); // must be dropped
   1091 	}
   1092 	p2 = gi.imageindex ("i_ctf2");
   1093 	e = G_Find(NULL, FOFS(classname), "item_flag_team2");
   1094 	if (e != NULL) {
   1095 		if (e->solid == SOLID_NOT) {
   1096 			int i;
   1097 
   1098 			// not at base
   1099 			// check if on player
   1100 			p2 = gi.imageindex ("i_ctf2d"); // default to dropped
   1101 			for (i = 1; i <= maxclients->value; i++)
   1102 				if (g_edicts[i].inuse &&
   1103 					g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) {
   1104 					// enemy has it
   1105 					p2 = gi.imageindex ("i_ctf2t");
   1106 					break;
   1107 				}
   1108 		} else if (e->spawnflags & DROPPED_ITEM)
   1109 			p2 = gi.imageindex ("i_ctf2d"); // must be dropped
   1110 	}
   1111 
   1112 
   1113 	ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
   1114 	ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
   1115 
   1116 	if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) {
   1117 		if (ctfgame.last_capture_team == CTF_TEAM1)
   1118 			if (level.framenum & 8)
   1119 				ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
   1120 			else
   1121 				ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0;
   1122 		else
   1123 			if (level.framenum & 8)
   1124 				ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
   1125 			else
   1126 				ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0;
   1127 	}
   1128 
   1129 	ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1;
   1130 	ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2;
   1131 
   1132 	ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0;
   1133 	if (ent->client->resp.ctf_team == CTF_TEAM1 &&
   1134 		ent->client->pers.inventory[ITEM_INDEX(flag2_item)] &&
   1135 		(level.framenum & 8))
   1136 		ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf2");
   1137 
   1138 	else if (ent->client->resp.ctf_team == CTF_TEAM2 &&
   1139 		ent->client->pers.inventory[ITEM_INDEX(flag1_item)] &&
   1140 		(level.framenum & 8))
   1141 		ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf1");
   1142 
   1143 	ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0;
   1144 	ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0;
   1145 	if (ent->client->resp.ctf_team == CTF_TEAM1)
   1146 		ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = gi.imageindex ("i_ctfj");
   1147 	else if (ent->client->resp.ctf_team == CTF_TEAM2)
   1148 		ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = gi.imageindex ("i_ctfj");
   1149 
   1150 	ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
   1151 	if (ent->client->resp.id_state)
   1152 		CTFSetIDView(ent);
   1153 }
   1154 
   1155 /*------------------------------------------------------------------------*/
   1156 
   1157 /*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32)
   1158 potential team1 spawning position for ctf games
   1159 */
   1160 void SP_info_player_team1(edict_t *self)
   1161 {
   1162 }
   1163 
   1164 /*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32)
   1165 potential team2 spawning position for ctf games
   1166 */
   1167 void SP_info_player_team2(edict_t *self)
   1168 {
   1169 }
   1170 
   1171 
   1172 /*------------------------------------------------------------------------*/
   1173 /* GRAPPLE																  */
   1174 /*------------------------------------------------------------------------*/
   1175 
   1176 // ent is player
   1177 void CTFPlayerResetGrapple(edict_t *ent)
   1178 {
   1179 	if (ent->client && ent->client->ctf_grapple)
   1180 		CTFResetGrapple(ent->client->ctf_grapple);
   1181 }
   1182 
   1183 // self is grapple, not player
   1184 void CTFResetGrapple(edict_t *self)
   1185 {
   1186 	if (self->owner->client->ctf_grapple) {
   1187 		float volume = 1.0;
   1188 		gclient_t *cl;
   1189 
   1190 		if (self->owner->client->silencer_shots)
   1191 			volume = 0.2;
   1192 
   1193 		gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0);
   1194 		cl = self->owner->client;
   1195 		cl->ctf_grapple = NULL;
   1196 		cl->ctf_grapplereleasetime = level.time;
   1197 		cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
   1198 		cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
   1199 		G_FreeEdict(self);
   1200 	}
   1201 }
   1202 
   1203 void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
   1204 {
   1205 	float volume = 1.0;
   1206 
   1207 	if (other == self->owner)
   1208 		return;
   1209 
   1210 	if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY)
   1211 		return;
   1212 
   1213 	if (surf && (surf->flags & SURF_SKY))
   1214 	{
   1215 		CTFResetGrapple(self);
   1216 		return;
   1217 	}
   1218 
   1219 	VectorCopy(vec3_origin, self->velocity);
   1220 
   1221 	PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
   1222 
   1223 	if (other->takedamage) {
   1224 		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE);
   1225 		CTFResetGrapple(self);
   1226 		return;
   1227 	}
   1228 
   1229 	self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook
   1230 	self->enemy = other;
   1231 
   1232 	self->solid = SOLID_NOT;
   1233 
   1234 	if (self->owner->client->silencer_shots)
   1235 		volume = 0.2;
   1236 
   1237 	gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0);
   1238 	gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0);
   1239 
   1240 	gi.WriteByte (svc_temp_entity);
   1241 	gi.WriteByte (TE_SPARKS);
   1242 	gi.WritePosition (self->s.origin);
   1243 	if (!plane)
   1244 		gi.WriteDir (vec3_origin);
   1245 	else
   1246 		gi.WriteDir (plane->normal);
   1247 	gi.multicast (self->s.origin, MULTICAST_PVS);
   1248 }
   1249 
   1250 // draw beam between grapple and self
   1251 void CTFGrappleDrawCable(edict_t *self)
   1252 {
   1253 	vec3_t	offset, start, end, f, r;
   1254 	vec3_t	dir;
   1255 	float	distance;
   1256 
   1257 	AngleVectors (self->owner->client->v_angle, f, r, NULL);
   1258 	VectorSet(offset, 16, 16, self->owner->viewheight-8);
   1259 	P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start);
   1260 
   1261 	VectorSubtract(start, self->owner->s.origin, offset);
   1262 
   1263 	VectorSubtract (start, self->s.origin, dir);
   1264 	distance = VectorLength(dir);
   1265 	// don't draw cable if close
   1266 	if (distance < 64)
   1267 		return;
   1268 
   1269 #if 0
   1270 	if (distance > 256)
   1271 		return;
   1272 
   1273 	// check for min/max pitch
   1274 	vectoangles (dir, angles);
   1275 	if (angles[0] < -180)
   1276 		angles[0] += 360;
   1277 	if (fabs(angles[0]) > 45)
   1278 		return;
   1279 
   1280 	trace_t	tr; //!!
   1281 
   1282 	tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT);
   1283 	if (tr.ent != self) {
   1284 		CTFResetGrapple(self);
   1285 		return;
   1286 	}
   1287 #endif
   1288 
   1289 	// adjust start for beam origin being in middle of a segment
   1290 //	VectorMA (start, 8, f, start);
   1291 
   1292 	VectorCopy (self->s.origin, end);
   1293 	// adjust end z for end spot since the monster is currently dead
   1294 //	end[2] = self->absmin[2] + self->size[2] / 2;
   1295 
   1296 	gi.WriteByte (svc_temp_entity);
   1297 #if 1 //def USE_GRAPPLE_CABLE
   1298 	gi.WriteByte (TE_GRAPPLE_CABLE);
   1299 	gi.WriteShort (self->owner - g_edicts);
   1300 	gi.WritePosition (self->owner->s.origin);
   1301 	gi.WritePosition (end);
   1302 	gi.WritePosition (offset);
   1303 #else
   1304 	gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
   1305 	gi.WriteShort (self - g_edicts);
   1306 	gi.WritePosition (end);
   1307 	gi.WritePosition (start);
   1308 #endif
   1309 	gi.multicast (self->s.origin, MULTICAST_PVS);
   1310 }
   1311 
   1312 void SV_AddGravity (edict_t *ent);
   1313 
   1314 // pull the player toward the grapple
   1315 void CTFGrapplePull(edict_t *self)
   1316 {
   1317 	vec3_t hookdir, v;
   1318 	float vlen;
   1319 
   1320 	if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 &&
   1321 		!self->owner->client->newweapon &&
   1322 		self->owner->client->weaponstate != WEAPON_FIRING &&
   1323 		self->owner->client->weaponstate != WEAPON_ACTIVATING) {
   1324 		CTFResetGrapple(self);
   1325 		return;
   1326 	}
   1327 
   1328 	if (self->enemy) {
   1329 		if (self->enemy->solid == SOLID_NOT) {
   1330 			CTFResetGrapple(self);
   1331 			return;
   1332 		}
   1333 		if (self->enemy->solid == SOLID_BBOX) {
   1334 			VectorScale(self->enemy->size, 0.5, v);
   1335 			VectorAdd(v, self->enemy->s.origin, v);
   1336 			VectorAdd(v, self->enemy->mins, self->s.origin);
   1337 			gi.linkentity (self);
   1338 		} else
   1339 			VectorCopy(self->enemy->velocity, self->velocity);
   1340 		if (self->enemy->takedamage &&
   1341 			!CheckTeamDamage (self->enemy, self->owner)) {
   1342 			float volume = 1.0;
   1343 
   1344 			if (self->owner->client->silencer_shots)
   1345 				volume = 0.2;
   1346 
   1347 			T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE);
   1348 			gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0);
   1349 		}
   1350 		if (self->enemy->deadflag) { // he died
   1351 			CTFResetGrapple(self);
   1352 			return;
   1353 		}
   1354 	}
   1355 
   1356 	CTFGrappleDrawCable(self);
   1357 
   1358 	if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
   1359 		// pull player toward grapple
   1360 		// this causes icky stuff with prediction, we need to extend
   1361 		// the prediction layer to include two new fields in the player
   1362 		// move stuff: a point and a velocity.  The client should add
   1363 		// that velociy in the direction of the point
   1364 		vec3_t forward, up;
   1365 
   1366 		AngleVectors (self->owner->client->v_angle, forward, NULL, up);
   1367 		VectorCopy(self->owner->s.origin, v);
   1368 		v[2] += self->owner->viewheight;
   1369 		VectorSubtract (self->s.origin, v, hookdir);
   1370 
   1371 		vlen = VectorLength(hookdir);
   1372 
   1373 		if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL &&
   1374 			vlen < 64) {
   1375 			float volume = 1.0;
   1376 
   1377 			if (self->owner->client->silencer_shots)
   1378 				volume = 0.2;
   1379 
   1380 			self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
   1381 			gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0);
   1382 			self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG;
   1383 		}
   1384 
   1385 		VectorNormalize (hookdir);
   1386 		VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir);
   1387 		VectorCopy(hookdir, self->owner->velocity);
   1388 		SV_AddGravity(self->owner);
   1389 	}
   1390 }
   1391 
   1392 void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
   1393 {
   1394 	edict_t	*grapple;
   1395 	trace_t	tr;
   1396 
   1397 	VectorNormalize (dir);
   1398 
   1399 	grapple = G_Spawn();
   1400 	VectorCopy (start, grapple->s.origin);
   1401 	VectorCopy (start, grapple->s.old_origin);
   1402 	vectoangles (dir, grapple->s.angles);
   1403 	VectorScale (dir, speed, grapple->velocity);
   1404 	grapple->movetype = MOVETYPE_FLYMISSILE;
   1405 	grapple->clipmask = MASK_SHOT;
   1406 	grapple->solid = SOLID_BBOX;
   1407 	grapple->s.effects |= effect;
   1408 	VectorClear (grapple->mins);
   1409 	VectorClear (grapple->maxs);
   1410 	grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2");
   1411 //	grapple->s.sound = gi.soundindex ("misc/lasfly.wav");
   1412 	grapple->owner = self;
   1413 	grapple->touch = CTFGrappleTouch;
   1414 //	grapple->nextthink = level.time + FRAMETIME;
   1415 //	grapple->think = CTFGrappleThink;
   1416 	grapple->dmg = damage;
   1417 	self->client->ctf_grapple = grapple;
   1418 	self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
   1419 	gi.linkentity (grapple);
   1420 
   1421 	tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT);
   1422 	if (tr.fraction < 1.0)
   1423 	{
   1424 		VectorMA (grapple->s.origin, -10, dir, grapple->s.origin);
   1425 		grapple->touch (grapple, tr.ent, NULL, NULL);
   1426 	}
   1427 }	
   1428 
   1429 void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect)
   1430 {
   1431 	vec3_t	forward, right;
   1432 	vec3_t	start;
   1433 	vec3_t	offset;
   1434 	float volume = 1.0;
   1435 
   1436 	if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
   1437 		return; // it's already out
   1438 
   1439 	AngleVectors (ent->client->v_angle, forward, right, NULL);
   1440 //	VectorSet(offset, 24, 16, ent->viewheight-8+2);
   1441 	VectorSet(offset, 24, 8, ent->viewheight-8+2);
   1442 	VectorAdd (offset, g_offset, offset);
   1443 	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
   1444 
   1445 	VectorScale (forward, -2, ent->client->kick_origin);
   1446 	ent->client->kick_angles[0] = -1;
   1447 
   1448 	if (ent->client->silencer_shots)
   1449 		volume = 0.2;
   1450 
   1451 	gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0);
   1452 	CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect);
   1453 
   1454 #if 0
   1455 	// send muzzle flash
   1456 	gi.WriteByte (svc_muzzleflash);
   1457 	gi.WriteShort (ent-g_edicts);
   1458 	gi.WriteByte (MZ_BLASTER);
   1459 	gi.multicast (ent->s.origin, MULTICAST_PVS);
   1460 #endif
   1461 
   1462 	PlayerNoise(ent, start, PNOISE_WEAPON);
   1463 }
   1464 
   1465 
   1466 void CTFWeapon_Grapple_Fire (edict_t *ent)
   1467 {
   1468 	int		damage;
   1469 
   1470 	damage = 10;
   1471 	CTFGrappleFire (ent, vec3_origin, damage, 0);
   1472 	ent->client->ps.gunframe++;
   1473 }
   1474 
   1475 void CTFWeapon_Grapple (edict_t *ent)
   1476 {
   1477 	static int	pause_frames[]	= {10, 18, 27, 0};
   1478 	static int	fire_frames[]	= {6, 0};
   1479 	int prevstate;
   1480 
   1481 	// if the the attack button is still down, stay in the firing frame
   1482 	if ((ent->client->buttons & BUTTON_ATTACK) && 
   1483 		ent->client->weaponstate == WEAPON_FIRING &&
   1484 		ent->client->ctf_grapple)
   1485 		ent->client->ps.gunframe = 9;
   1486 
   1487 	if (!(ent->client->buttons & BUTTON_ATTACK) && 
   1488 		ent->client->ctf_grapple) {
   1489 		CTFResetGrapple(ent->client->ctf_grapple);
   1490 		if (ent->client->weaponstate == WEAPON_FIRING)
   1491 			ent->client->weaponstate = WEAPON_READY;
   1492 	}
   1493 
   1494 
   1495 	if (ent->client->newweapon && 
   1496 		ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY &&
   1497 		ent->client->weaponstate == WEAPON_FIRING) {
   1498 		// he wants to change weapons while grappled
   1499 		ent->client->weaponstate = WEAPON_DROPPING;
   1500 		ent->client->ps.gunframe = 32;
   1501 	}
   1502 
   1503 	prevstate = ent->client->weaponstate;
   1504 	Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, 
   1505 		CTFWeapon_Grapple_Fire);
   1506 
   1507 	// if we just switched back to grapple, immediately go to fire frame
   1508 	if (prevstate == WEAPON_ACTIVATING &&
   1509 		ent->client->weaponstate == WEAPON_READY &&
   1510 		ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
   1511 		if (!(ent->client->buttons & BUTTON_ATTACK))
   1512 			ent->client->ps.gunframe = 9;
   1513 		else
   1514 			ent->client->ps.gunframe = 5;
   1515 		ent->client->weaponstate = WEAPON_FIRING;
   1516 	}
   1517 }
   1518 
   1519 void CTFTeam_f (edict_t *ent)
   1520 {
   1521 	char *t, *s;
   1522 	int desired_team;
   1523 
   1524 	t = gi.args();
   1525 	if (!*t) {
   1526 		gi.cprintf(ent, PRINT_HIGH, "You are on the %s team.\n",
   1527 			CTFTeamName(ent->client->resp.ctf_team));
   1528 		return;
   1529 	}
   1530 
   1531 	if (ctfgame.match > MATCH_SETUP) {
   1532 		gi.cprintf(ent, PRINT_HIGH, "Can't change teams in a match.\n");
   1533 		return;
   1534 	}
   1535 
   1536 	if (Q_stricmp(t, "red") == 0)
   1537 		desired_team = CTF_TEAM1;
   1538 	else if (Q_stricmp(t, "blue") == 0)
   1539 		desired_team = CTF_TEAM2;
   1540 	else {
   1541 		gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t);
   1542 		return;
   1543 	}
   1544 
   1545 	if (ent->client->resp.ctf_team == desired_team) {
   1546 		gi.cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n",
   1547 			CTFTeamName(ent->client->resp.ctf_team));
   1548 		return;
   1549 	}
   1550 
   1551 ////
   1552 	ent->svflags = 0;
   1553 	ent->flags &= ~FL_GODMODE;
   1554 	ent->client->resp.ctf_team = desired_team;
   1555 	ent->client->resp.ctf_state = 0;
   1556 	s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
   1557 	CTFAssignSkin(ent, s);
   1558 
   1559 	if (ent->solid == SOLID_NOT) { // spectator
   1560 		PutClientInServer (ent);
   1561 		// add a teleportation effect
   1562 		ent->s.event = EV_PLAYER_TELEPORT;
   1563 		// hold in place briefly
   1564 		ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
   1565 		ent->client->ps.pmove.pm_time = 14;
   1566 		gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n",
   1567 			ent->client->pers.netname, CTFTeamName(desired_team));
   1568 		return;
   1569 	}
   1570 
   1571 	ent->health = 0;
   1572 	player_die (ent, ent, ent, 100000, vec3_origin);
   1573 	// don't even bother waiting for death frames
   1574 	ent->deadflag = DEAD_DEAD;
   1575 	respawn (ent);
   1576 
   1577 	ent->client->resp.score = 0;
   1578 
   1579 	gi.bprintf(PRINT_HIGH, "%s changed to the %s team.\n",
   1580 		ent->client->pers.netname, CTFTeamName(desired_team));
   1581 }
   1582 
   1583 /*
   1584 ==================
   1585 CTFScoreboardMessage
   1586 ==================
   1587 */
   1588 void CTFScoreboardMessage (edict_t *ent, edict_t *killer)
   1589 {
   1590 	char	entry[1024];
   1591 	char	string[1400];
   1592 	int		len;
   1593 	int		i, j, k, n;
   1594 	int		sorted[2][MAX_CLIENTS];
   1595 	int		sortedscores[2][MAX_CLIENTS];
   1596 	int		score, total[2], totalscore[2];
   1597 	int		last[2];
   1598 	gclient_t	*cl;
   1599 	edict_t		*cl_ent;
   1600 	int team;
   1601 	int maxsize = 1000;
   1602 
   1603 	// sort the clients by team and score
   1604 	total[0] = total[1] = 0;
   1605 	last[0] = last[1] = 0;
   1606 	totalscore[0] = totalscore[1] = 0;
   1607 	for (i=0 ; i<game.maxclients ; i++)
   1608 	{
   1609 		cl_ent = g_edicts + 1 + i;
   1610 		if (!cl_ent->inuse)
   1611 			continue;
   1612 		if (game.clients[i].resp.ctf_team == CTF_TEAM1)
   1613 			team = 0;
   1614 		else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
   1615 			team = 1;
   1616 		else
   1617 			continue; // unknown team?
   1618 
   1619 		score = game.clients[i].resp.score;
   1620 		for (j=0 ; j<total[team] ; j++)
   1621 		{
   1622 			if (score > sortedscores[team][j])
   1623 				break;
   1624 		}
   1625 		for (k=total[team] ; k>j ; k--)
   1626 		{
   1627 			sorted[team][k] = sorted[team][k-1];
   1628 			sortedscores[team][k] = sortedscores[team][k-1];
   1629 		}
   1630 		sorted[team][j] = i;
   1631 		sortedscores[team][j] = score;
   1632 		totalscore[team] += score;
   1633 		total[team]++;
   1634 	}
   1635 
   1636 	// print level name and exit rules
   1637 	// add the clients in sorted order
   1638 	*string = 0;
   1639 	len = 0;
   1640 
   1641 	// team one
   1642 	sprintf(string, "if 24 xv 8 yv 8 pic 24 endif "
   1643 		"xv 40 yv 28 string \"%4d/%-3d\" "
   1644 		"xv 98 yv 12 num 2 18 "
   1645 		"if 25 xv 168 yv 8 pic 25 endif "
   1646 		"xv 200 yv 28 string \"%4d/%-3d\" "
   1647 		"xv 256 yv 12 num 2 20 ",
   1648 		totalscore[0], total[0],
   1649 		totalscore[1], total[1]);
   1650 	len = strlen(string);
   1651 
   1652 	for (i=0 ; i<16 ; i++)
   1653 	{
   1654 		if (i >= total[0] && i >= total[1])
   1655 			break; // we're done
   1656 
   1657 #if 0 //ndef NEW_SCORE
   1658 		// set up y
   1659 		sprintf(entry, "yv %d ", 42 + i * 8);
   1660 		if (maxsize - len > strlen(entry)) {
   1661 			strcat(string, entry);
   1662 			len = strlen(string);
   1663 		}
   1664 #else
   1665 		*entry = 0;
   1666 #endif
   1667 
   1668 		// left side
   1669 		if (i < total[0]) {
   1670 			cl = &game.clients[sorted[0][i]];
   1671 			cl_ent = g_edicts + 1 + sorted[0][i];
   1672 
   1673 #if 0 //ndef NEW_SCORE
   1674 			sprintf(entry+strlen(entry),
   1675 			"xv 0 %s \"%3d %3d %-12.12s\" ",
   1676 			(cl_ent == ent) ? "string2" : "string",
   1677 			cl->resp.score, 
   1678 			(cl->ping > 999) ? 999 : cl->ping, 
   1679 			cl->pers.netname);
   1680 
   1681 			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
   1682 				strcat(entry, "xv 56 picn sbfctf2 ");
   1683 #else
   1684 			sprintf(entry+strlen(entry),
   1685 				"ctf 0 %d %d %d %d ",
   1686 				42 + i * 8,
   1687 				sorted[0][i],
   1688 				cl->resp.score,
   1689 				cl->ping > 999 ? 999 : cl->ping);
   1690 
   1691 			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
   1692 				sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ",
   1693 					42 + i * 8);
   1694 #endif
   1695 
   1696 			if (maxsize - len > strlen(entry)) {
   1697 				strcat(string, entry);
   1698 				len = strlen(string);
   1699 				last[0] = i;
   1700 			}
   1701 		}
   1702 
   1703 		// right side
   1704 		if (i < total[1]) {
   1705 			cl = &game.clients[sorted[1][i]];
   1706 			cl_ent = g_edicts + 1 + sorted[1][i];
   1707 
   1708 #if 0 //ndef NEW_SCORE
   1709 			sprintf(entry+strlen(entry),
   1710 			"xv 160 %s \"%3d %3d %-12.12s\" ",
   1711 			(cl_ent == ent) ? "string2" : "string",
   1712 			cl->resp.score, 
   1713 			(cl->ping > 999) ? 999 : cl->ping, 
   1714 			cl->pers.netname);
   1715 
   1716 			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
   1717 				strcat(entry, "xv 216 picn sbfctf1 ");
   1718 
   1719 #else
   1720 
   1721 			sprintf(entry+strlen(entry),
   1722 				"ctf 160 %d %d %d %d ",
   1723 				42 + i * 8,
   1724 				sorted[1][i],
   1725 				cl->resp.score,
   1726 				cl->ping > 999 ? 999 : cl->ping);
   1727 
   1728 			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
   1729 				sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ",
   1730 					42 + i * 8);
   1731 #endif
   1732 			if (maxsize - len > strlen(entry)) {
   1733 				strcat(string, entry);
   1734 				len = strlen(string);
   1735 				last[1] = i;
   1736 			}
   1737 		}
   1738 	}
   1739 
   1740 	// put in spectators if we have enough room
   1741 	if (last[0] > last[1])
   1742 		j = last[0];
   1743 	else
   1744 		j = last[1];
   1745 	j = (j + 2) * 8 + 42;
   1746 
   1747 	k = n = 0;
   1748 	if (maxsize - len > 50) {
   1749 		for (i = 0; i < maxclients->value; i++) {
   1750 			cl_ent = g_edicts + 1 + i;
   1751 			cl = &game.clients[i];
   1752 			if (!cl_ent->inuse ||
   1753 				cl_ent->solid != SOLID_NOT ||
   1754 				cl_ent->client->resp.ctf_team != CTF_NOTEAM)
   1755 				continue;
   1756 
   1757 			if (!k) {
   1758 				k = 1;
   1759 				sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j);
   1760 				strcat(string, entry);
   1761 				len = strlen(string);
   1762 				j += 8;
   1763 			}
   1764 
   1765 			sprintf(entry+strlen(entry),
   1766 				"ctf %d %d %d %d %d ",
   1767 				(n & 1) ? 160 : 0, // x
   1768 				j, // y
   1769 				i, // playernum
   1770 				cl->resp.score,
   1771 				cl->ping > 999 ? 999 : cl->ping);
   1772 			if (maxsize - len > strlen(entry)) {
   1773 				strcat(string, entry);
   1774 				len = strlen(string);
   1775 			}
   1776 			
   1777 			if (n & 1)
   1778 				j += 8;
   1779 			n++;
   1780 		}
   1781 	}
   1782 
   1783 	if (total[0] - last[0] > 1) // couldn't fit everyone
   1784 		sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ",
   1785 			42 + (last[0]+1)*8, total[0] - last[0] - 1);
   1786 	if (total[1] - last[1] > 1) // couldn't fit everyone
   1787 		sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ",
   1788 			42 + (last[1]+1)*8, total[1] - last[1] - 1);
   1789 
   1790 	gi.WriteByte (svc_layout);
   1791 	gi.WriteString (string);
   1792 }
   1793 
   1794 /*------------------------------------------------------------------------*/
   1795 /* TECH																	  */
   1796 /*------------------------------------------------------------------------*/
   1797 
   1798 void CTFHasTech(edict_t *who)
   1799 {
   1800 	if (level.time - who->client->ctf_lasttechmsg > 2) {
   1801 		gi.centerprintf(who, "You already have a TECH powerup.");
   1802 		who->client->ctf_lasttechmsg = level.time;
   1803 	}
   1804 }
   1805 
   1806 gitem_t *CTFWhat_Tech(edict_t *ent)
   1807 {
   1808 	gitem_t *tech;
   1809 	int i;
   1810 
   1811 	i = 0;
   1812 	while (tnames[i]) {
   1813 		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
   1814 			ent->client->pers.inventory[ITEM_INDEX(tech)]) {
   1815 			return tech;
   1816 		}
   1817 		i++;
   1818 	}
   1819 	return NULL;
   1820 }
   1821 
   1822 qboolean CTFPickup_Tech (edict_t *ent, edict_t *other)
   1823 {
   1824 	gitem_t *tech;
   1825 	int i;
   1826 
   1827 	i = 0;
   1828 	while (tnames[i]) {
   1829 		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
   1830 			other->client->pers.inventory[ITEM_INDEX(tech)]) {
   1831 			CTFHasTech(other);
   1832 			return false; // has this one
   1833 		}
   1834 		i++;
   1835 	}
   1836 	
   1837 	// client only gets one tech
   1838 	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
   1839 	other->client->ctf_regentime = level.time;
   1840 	return true;
   1841 }
   1842 
   1843 static void SpawnTech(gitem_t *item, edict_t *spot);
   1844 
   1845 static edict_t *FindTechSpawn(void)
   1846 {
   1847 	edict_t *spot = NULL;
   1848 	int i = rand() % 16;
   1849 
   1850 	while (i--)
   1851 		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
   1852 	if (!spot)
   1853 		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
   1854 	return spot;
   1855 }
   1856 
   1857 static void TechThink(edict_t *tech)
   1858 {
   1859 	edict_t *spot;
   1860 
   1861 	if ((spot = FindTechSpawn()) != NULL) {
   1862 		SpawnTech(tech->item, spot);
   1863 		G_FreeEdict(tech);
   1864 	} else {
   1865 		tech->nextthink = level.time + CTF_TECH_TIMEOUT;
   1866 		tech->think = TechThink;
   1867 	}
   1868 }
   1869 
   1870 void CTFDrop_Tech(edict_t *ent, gitem_t *item)
   1871 {
   1872 	edict_t *tech;
   1873 
   1874 	tech = Drop_Item(ent, item);
   1875 	tech->nextthink = level.time + CTF_TECH_TIMEOUT;
   1876 	tech->think = TechThink;
   1877 	ent->client->pers.inventory[ITEM_INDEX(item)] = 0;
   1878 }
   1879 
   1880 void CTFDeadDropTech(edict_t *ent)
   1881 {
   1882 	gitem_t *tech;
   1883 	edict_t *dropped;
   1884 	int i;
   1885 
   1886 	i = 0;
   1887 	while (tnames[i]) {
   1888 		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
   1889 			ent->client->pers.inventory[ITEM_INDEX(tech)]) {
   1890 			dropped = Drop_Item(ent, tech);
   1891 			// hack the velocity to make it bounce random
   1892 			dropped->velocity[0] = (rand() % 600) - 300;
   1893 			dropped->velocity[1] = (rand() % 600) - 300;
   1894 			dropped->nextthink = level.time + CTF_TECH_TIMEOUT;
   1895 			dropped->think = TechThink;
   1896 			dropped->owner = NULL;
   1897 			ent->client->pers.inventory[ITEM_INDEX(tech)] = 0;
   1898 		}
   1899 		i++;
   1900 	}
   1901 }
   1902 
   1903 static void SpawnTech(gitem_t *item, edict_t *spot)
   1904 {
   1905 	edict_t	*ent;
   1906 	vec3_t	forward, right;
   1907 	vec3_t  angles;
   1908 
   1909 	ent = G_Spawn();
   1910 
   1911 	ent->classname = item->classname;
   1912 	ent->item = item;
   1913 	ent->spawnflags = DROPPED_ITEM;
   1914 	ent->s.effects = item->world_model_flags;
   1915 	ent->s.renderfx = RF_GLOW;
   1916 	VectorSet (ent->mins, -15, -15, -15);
   1917 	VectorSet (ent->maxs, 15, 15, 15);
   1918 	gi.setmodel (ent, ent->item->world_model);
   1919 	ent->solid = SOLID_TRIGGER;
   1920 	ent->movetype = MOVETYPE_TOSS;  
   1921 	ent->touch = Touch_Item;
   1922 	ent->owner = ent;
   1923 
   1924 	angles[0] = 0;
   1925 	angles[1] = rand() % 360;
   1926 	angles[2] = 0;
   1927 
   1928 	AngleVectors (angles, forward, right, NULL);
   1929 	VectorCopy (spot->s.origin, ent->s.origin);
   1930 	ent->s.origin[2] += 16;
   1931 	VectorScale (forward, 100, ent->velocity);
   1932 	ent->velocity[2] = 300;
   1933 
   1934 	ent->nextthink = level.time + CTF_TECH_TIMEOUT;
   1935 	ent->think = TechThink;
   1936 
   1937 	gi.linkentity (ent);
   1938 }
   1939 
   1940 static void SpawnTechs(edict_t *ent)
   1941 {
   1942 	gitem_t *tech;
   1943 	edict_t *spot;
   1944 	int i;
   1945 
   1946 	i = 0;
   1947 	while (tnames[i]) {
   1948 		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
   1949 			(spot = FindTechSpawn()) != NULL)
   1950 			SpawnTech(tech, spot);
   1951 		i++;
   1952 	}
   1953 	if (ent)
   1954 		G_FreeEdict(ent);
   1955 }
   1956 
   1957 // frees the passed edict!
   1958 void CTFRespawnTech(edict_t *ent)
   1959 {
   1960 	edict_t *spot;
   1961 
   1962 	if ((spot = FindTechSpawn()) != NULL)
   1963 		SpawnTech(ent->item, spot);
   1964 	G_FreeEdict(ent);
   1965 }
   1966 
   1967 void CTFSetupTechSpawn(void)
   1968 {
   1969 	edict_t *ent;
   1970 
   1971 	if (((int)dmflags->value & DF_CTF_NO_TECH))
   1972 		return;
   1973 
   1974 	ent = G_Spawn();
   1975 	ent->nextthink = level.time + 2;
   1976 	ent->think = SpawnTechs;
   1977 }
   1978 
   1979 void CTFResetTech(void)
   1980 {
   1981 	edict_t *ent;
   1982 	int i;
   1983 
   1984 	for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) {
   1985 		if (ent->inuse)
   1986 			if (ent->item && (ent->item->flags & IT_TECH))
   1987 				G_FreeEdict(ent);
   1988 	}
   1989 	SpawnTechs(NULL);
   1990 }
   1991 
   1992 int CTFApplyResistance(edict_t *ent, int dmg)
   1993 {
   1994 	static gitem_t *tech = NULL;
   1995 	float volume = 1.0;
   1996 
   1997 	if (ent->client && ent->client->silencer_shots)
   1998 		volume = 0.2;
   1999 
   2000 	if (!tech)
   2001 		tech = FindItemByClassname("item_tech1");
   2002 	if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
   2003 		// make noise
   2004 	   	gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0);
   2005 		return dmg / 2;
   2006 	}
   2007 	return dmg;
   2008 }
   2009 
   2010 int CTFApplyStrength(edict_t *ent, int dmg)
   2011 {
   2012 	static gitem_t *tech = NULL;
   2013 
   2014 	if (!tech)
   2015 		tech = FindItemByClassname("item_tech2");
   2016 	if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
   2017 		return dmg * 2;
   2018 	}
   2019 	return dmg;
   2020 }
   2021 
   2022 qboolean CTFApplyStrengthSound(edict_t *ent)
   2023 {
   2024 	static gitem_t *tech = NULL;
   2025 	float volume = 1.0;
   2026 
   2027 	if (ent->client && ent->client->silencer_shots)
   2028 		volume = 0.2;
   2029 
   2030 	if (!tech)
   2031 		tech = FindItemByClassname("item_tech2");
   2032 	if (tech && ent->client &&
   2033 		ent->client->pers.inventory[ITEM_INDEX(tech)]) {
   2034 		if (ent->client->ctf_techsndtime < level.time) {
   2035 			ent->client->ctf_techsndtime = level.time + 1;
   2036 			if (ent->client->quad_framenum > level.framenum)
   2037 				gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0);
   2038 			else
   2039 				gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0);
   2040 		}
   2041 		return true;
   2042 	}
   2043 	return false;
   2044 }
   2045 
   2046 
   2047 qboolean CTFApplyHaste(edict_t *ent)
   2048 {
   2049 	static gitem_t *tech = NULL;
   2050 
   2051 	if (!tech)
   2052 		tech = FindItemByClassname("item_tech3");
   2053 	if (tech && ent->client &&
   2054 		ent->client->pers.inventory[ITEM_INDEX(tech)])
   2055 		return true;
   2056 	return false;
   2057 }
   2058 
   2059 void CTFApplyHasteSound(edict_t *ent)
   2060 {
   2061 	static gitem_t *tech = NULL;
   2062 	float volume = 1.0;
   2063 
   2064 	if (ent->client && ent->client->silencer_shots)
   2065 		volume = 0.2;
   2066 
   2067 	if (!tech)
   2068 		tech = FindItemByClassname("item_tech3");
   2069 	if (tech && ent->client &&
   2070 		ent->client->pers.inventory[ITEM_INDEX(tech)] &&
   2071 		ent->client->ctf_techsndtime < level.time) {
   2072 		ent->client->ctf_techsndtime = level.time + 1;
   2073 		gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0);
   2074 	}
   2075 }
   2076 
   2077 void CTFApplyRegeneration(edict_t *ent)
   2078 {
   2079 	static gitem_t *tech = NULL;
   2080 	qboolean noise = false;
   2081 	gclient_t *client;
   2082 	int index;
   2083 	float volume = 1.0;
   2084 
   2085 	client = ent->client;
   2086 	if (!client)
   2087 		return;
   2088 
   2089 	if (ent->client->silencer_shots)
   2090 		volume = 0.2;
   2091 
   2092 	if (!tech)
   2093 		tech = FindItemByClassname("item_tech4");
   2094 	if (tech && client->pers.inventory[ITEM_INDEX(tech)]) {
   2095 		if (client->ctf_regentime < level.time) {
   2096 			client->ctf_regentime = level.time;
   2097 			if (ent->health < 150) {
   2098 				ent->health += 5;
   2099 				if (ent->health > 150)
   2100 					ent->health = 150;
   2101 				client->ctf_regentime += 0.5;
   2102 				noise = true;
   2103 			}
   2104 			index = ArmorIndex (ent);
   2105 			if (index && client->pers.inventory[index] < 150) {
   2106 				client->pers.inventory[index] += 5;
   2107 				if (client->pers.inventory[index] > 150)
   2108 					client->pers.inventory[index] = 150;
   2109 				client->ctf_regentime += 0.5;
   2110 				noise = true;
   2111 			}
   2112 		}
   2113 		if (noise && ent->client->ctf_techsndtime < level.time) {
   2114 			ent->client->ctf_techsndtime = level.time + 1;
   2115 			gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0);
   2116 		}
   2117 	}
   2118 }
   2119 
   2120 qboolean CTFHasRegeneration(edict_t *ent)
   2121 {
   2122 	static gitem_t *tech = NULL;
   2123 
   2124 	if (!tech)
   2125 		tech = FindItemByClassname("item_tech4");
   2126 	if (tech && ent->client &&
   2127 		ent->client->pers.inventory[ITEM_INDEX(tech)])
   2128 		return true;
   2129 	return false;
   2130 }
   2131 
   2132 /*
   2133 ======================================================================
   2134 
   2135 SAY_TEAM
   2136 
   2137 ======================================================================
   2138 */
   2139 
   2140 // This array is in 'importance order', it indicates what items are
   2141 // more important when reporting their names.
   2142 struct {
   2143 	char *classname;
   2144 	int priority;
   2145 } loc_names[] = 
   2146 {
   2147 	{	"item_flag_team1",			1 },
   2148 	{	"item_flag_team2",			1 },
   2149 	{	"item_quad",				2 }, 
   2150 	{	"item_invulnerability",		2 },
   2151 	{	"weapon_bfg",				3 },
   2152 	{	"weapon_railgun",			4 },
   2153 	{	"weapon_rocketlauncher",	4 },
   2154 	{	"weapon_hyperblaster",		4 },
   2155 	{	"weapon_chaingun",			4 },
   2156 	{	"weapon_grenadelauncher",	4 },
   2157 	{	"weapon_machinegun",		4 },
   2158 	{	"weapon_supershotgun",		4 },
   2159 	{	"weapon_shotgun",			4 },
   2160 	{	"item_power_screen",		5 },
   2161 	{	"item_power_shield",		5 },
   2162 	{	"item_armor_body",			6 },
   2163 	{	"item_armor_combat",		6 },
   2164 	{	"item_armor_jacket",		6 },
   2165 	{	"item_silencer",			7 },
   2166 	{	"item_breather",			7 },
   2167 	{	"item_enviro",				7 },
   2168 	{	"item_adrenaline",			7 },
   2169 	{	"item_bandolier",			8 },
   2170 	{	"item_pack",				8 },
   2171 	{ NULL, 0 }
   2172 };
   2173 
   2174 
   2175 static void CTFSay_Team_Location(edict_t *who, char *buf)
   2176 {
   2177 	edict_t *what = NULL;
   2178 	edict_t *hot = NULL;
   2179 	float hotdist = 999999, newdist;
   2180 	vec3_t v;
   2181 	int hotindex = 999;
   2182 	int i;
   2183 	gitem_t *item;
   2184 	int nearteam = -1;
   2185 	edict_t *flag1, *flag2;
   2186 	qboolean hotsee = false;
   2187 	qboolean cansee;
   2188 
   2189 	while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) {
   2190 		// find what in loc_classnames
   2191 		for (i = 0; loc_names[i].classname; i++)
   2192 			if (strcmp(what->classname, loc_names[i].classname) == 0)
   2193 				break;
   2194 		if (!loc_names[i].classname)
   2195 			continue;
   2196 		// something we can see get priority over something we can't
   2197 		cansee = loc_CanSee(what, who);
   2198 		if (cansee && !hotsee) {
   2199 			hotsee = true;
   2200 			hotindex = loc_names[i].priority;
   2201 			hot = what;
   2202 			VectorSubtract(what->s.origin, who->s.origin, v);
   2203 			hotdist = VectorLength(v);
   2204 			continue;
   2205 		}
   2206 		// if we can't see this, but we have something we can see, skip it
   2207 		if (hotsee && !cansee)
   2208 			continue;
   2209 		if (hotsee && hotindex < loc_names[i].priority)
   2210 			continue;
   2211 		VectorSubtract(what->s.origin, who->s.origin, v);
   2212 		newdist = VectorLength(v);
   2213 		if (newdist < hotdist || 
   2214 			(cansee && loc_names[i].priority < hotindex)) {
   2215 			hot = what;
   2216 			hotdist = newdist;
   2217 			hotindex = i;
   2218 			hotsee = loc_CanSee(hot, who);
   2219 		}
   2220 	}
   2221 
   2222 	if (!hot) {
   2223 		strcpy(buf, "nowhere");
   2224 		return;
   2225 	}
   2226 
   2227 	// we now have the closest item
   2228 	// see if there's more than one in the map, if so
   2229 	// we need to determine what team is closest
   2230 	what = NULL;
   2231 	while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) {
   2232 		if (what == hot)
   2233 			continue;
   2234 		// if we are here, there is more than one, find out if hot
   2235 		// is closer to red flag or blue flag
   2236 		if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL &&
   2237 			(flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) {
   2238 			VectorSubtract(hot->s.origin, flag1->s.origin, v);
   2239 			hotdist = VectorLength(v);
   2240 			VectorSubtract(hot->s.origin, flag2->s.origin, v);
   2241 			newdist = VectorLength(v);
   2242 			if (hotdist < newdist)
   2243 				nearteam = CTF_TEAM1;
   2244 			else if (hotdist > newdist)
   2245 				nearteam = CTF_TEAM2;
   2246 		}
   2247 		break;
   2248 	}
   2249 
   2250 	if ((item = FindItemByClassname(hot->classname)) == NULL) {
   2251 		strcpy(buf, "nowhere");
   2252 		return;
   2253 	}
   2254 
   2255 	// in water?
   2256 	if (who->waterlevel)
   2257 		strcpy(buf, "in the water ");
   2258 	else
   2259 		*buf = 0;
   2260 
   2261 	// near or above
   2262 	VectorSubtract(who->s.origin, hot->s.origin, v);
   2263 	if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1]))
   2264 		if (v[2] > 0)
   2265 			strcat(buf, "above ");
   2266 		else
   2267 			strcat(buf, "below ");
   2268 	else
   2269 		strcat(buf, "near ");
   2270 
   2271 	if (nearteam == CTF_TEAM1)
   2272 		strcat(buf, "the red ");
   2273 	else if (nearteam == CTF_TEAM2)
   2274 		strcat(buf, "the blue ");
   2275 	else
   2276 		strcat(buf, "the ");
   2277 
   2278 	strcat(buf, item->pickup_name);
   2279 }
   2280 
   2281 static void CTFSay_Team_Armor(edict_t *who, char *buf)
   2282 {
   2283 	gitem_t		*item;
   2284 	int			index, cells;
   2285 	int			power_armor_type;
   2286 
   2287 	*buf = 0;
   2288 
   2289 	power_armor_type = PowerArmorType (who);
   2290 	if (power_armor_type)
   2291 	{
   2292 		cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
   2293 		if (cells)
   2294 			sprintf(buf+strlen(buf), "%s with %i cells ",
   2295 				(power_armor_type == POWER_ARMOR_SCREEN) ?
   2296 				"Power Screen" : "Power Shield", cells);
   2297 	}
   2298 
   2299 	index = ArmorIndex (who);
   2300 	if (index)
   2301 	{
   2302 		item = GetItemByIndex (index);
   2303 		if (item) {
   2304 			if (*buf)
   2305 				strcat(buf, "and ");
   2306 			sprintf(buf+strlen(buf), "%i units of %s",
   2307 				who->client->pers.inventory[index], item->pickup_name);
   2308 		}
   2309 	}
   2310 
   2311 	if (!*buf)
   2312 		strcpy(buf, "no armor");
   2313 }
   2314 
   2315 static void CTFSay_Team_Health(edict_t *who, char *buf)
   2316 {
   2317 	if (who->health <= 0)
   2318 		strcpy(buf, "dead");
   2319 	else
   2320 		sprintf(buf, "%i health", who->health);
   2321 }
   2322 
   2323 static void CTFSay_Team_Tech(edict_t *who, char *buf)
   2324 {
   2325 	gitem_t *tech;
   2326 	int i;
   2327 
   2328 	// see if the player has a tech powerup
   2329 	i = 0;
   2330 	while (tnames[i]) {
   2331 		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
   2332 			who->client->pers.inventory[ITEM_INDEX(tech)]) {
   2333 			sprintf(buf, "the %s", tech->pickup_name);
   2334 			return;
   2335 		}
   2336 		i++;
   2337 	}
   2338 	strcpy(buf, "no powerup");
   2339 }
   2340 
   2341 static void CTFSay_Team_Weapon(edict_t *who, char *buf)
   2342 {
   2343 	if (who->client->pers.weapon)
   2344 		strcpy(buf, who->client->pers.weapon->pickup_name);
   2345 	else
   2346 		strcpy(buf, "none");
   2347 }
   2348 
   2349 static void CTFSay_Team_Sight(edict_t *who, char *buf)
   2350 {
   2351 	int i;
   2352 	edict_t *targ;
   2353 	int n = 0;
   2354 	char s[1024];
   2355 	char s2[1024];
   2356 
   2357 	*s = *s2 = 0;
   2358 	for (i = 1; i <= maxclients->value; i++) {
   2359 		targ = g_edicts + i;
   2360 		if (!targ->inuse || 
   2361 			targ == who ||
   2362 			!loc_CanSee(targ, who))
   2363 			continue;
   2364 		if (*s2) {
   2365 			if (strlen(s) + strlen(s2) + 3 < sizeof(s)) {
   2366 				if (n)
   2367 					strcat(s, ", ");
   2368 				strcat(s, s2);
   2369 				*s2 = 0;
   2370 			}
   2371 			n++;
   2372 		}
   2373 		strcpy(s2, targ->client->pers.netname);
   2374 	}
   2375 	if (*s2) {
   2376 		if (strlen(s) + strlen(s2) + 6 < sizeof(s)) {
   2377 			if (n)
   2378 				strcat(s, " and ");
   2379 			strcat(s, s2);
   2380 		}
   2381 		strcpy(buf, s);
   2382 	} else
   2383 		strcpy(buf, "no one");
   2384 }
   2385 
   2386 void CTFSay_Team(edict_t *who, char *msg)
   2387 {
   2388 	char outmsg[1024];
   2389 	char buf[1024];
   2390 	int i;
   2391 	char *p;
   2392 	edict_t *cl_ent;
   2393 
   2394 	if (CheckFlood(who))
   2395 		return;
   2396 
   2397 	outmsg[0] = 0;
   2398 
   2399 	if (*msg == '\"') {
   2400 		msg[strlen(msg) - 1] = 0;
   2401 		msg++;
   2402 	}
   2403 
   2404 	for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) {
   2405 		if (*msg == '%') {
   2406 			switch (*++msg) {
   2407 				case 'l' :
   2408 				case 'L' :
   2409 					CTFSay_Team_Location(who, buf);
   2410 					strcpy(p, buf);
   2411 					p += strlen(buf);
   2412 					break;
   2413 				case 'a' :
   2414 				case 'A' :
   2415 					CTFSay_Team_Armor(who, buf);
   2416 					strcpy(p, buf);
   2417 					p += strlen(buf);
   2418 					break;
   2419 				case 'h' :
   2420 				case 'H' :
   2421 					CTFSay_Team_Health(who, buf);
   2422 					strcpy(p, buf);
   2423 					p += strlen(buf);
   2424 					break;
   2425 				case 't' :
   2426 				case 'T' :
   2427 					CTFSay_Team_Tech(who, buf);
   2428 					strcpy(p, buf);
   2429 					p += strlen(buf);
   2430 					break;
   2431 				case 'w' :
   2432 				case 'W' :
   2433 					CTFSay_Team_Weapon(who, buf);
   2434 					strcpy(p, buf);
   2435 					p += strlen(buf);
   2436 					break;
   2437 
   2438 				case 'n' :
   2439 				case 'N' :
   2440 					CTFSay_Team_Sight(who, buf);
   2441 					strcpy(p, buf);
   2442 					p += strlen(buf);
   2443 					break;
   2444 
   2445 				default :
   2446 					*p++ = *msg;
   2447 			}
   2448 		} else
   2449 			*p++ = *msg;
   2450 	}
   2451 	*p = 0;
   2452 
   2453 	for (i = 0; i < maxclients->value; i++) {
   2454 		cl_ent = g_edicts + 1 + i;
   2455 		if (!cl_ent->inuse)
   2456 			continue;
   2457 		if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team)
   2458 			gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", 
   2459 				who->client->pers.netname, outmsg);
   2460 	}
   2461 }
   2462 
   2463 /*-----------------------------------------------------------------------*/
   2464 /*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2
   2465 The origin is the bottom of the banner.
   2466 The banner is 248 tall.
   2467 */
   2468 static void misc_ctf_banner_think (edict_t *ent)
   2469 {
   2470 	ent->s.frame = (ent->s.frame + 1) % 16;
   2471 	ent->nextthink = level.time + FRAMETIME;
   2472 }
   2473 
   2474 void SP_misc_ctf_banner (edict_t *ent)
   2475 {
   2476 	ent->movetype = MOVETYPE_NONE;
   2477 	ent->solid = SOLID_NOT;
   2478 	ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2");
   2479 	if (ent->spawnflags & 1) // team2
   2480 		ent->s.skinnum = 1;
   2481 
   2482 	ent->s.frame = rand() % 16;
   2483 	gi.linkentity (ent);
   2484 
   2485 	ent->think = misc_ctf_banner_think;
   2486 	ent->nextthink = level.time + FRAMETIME;
   2487 }
   2488 
   2489 /*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2
   2490 The origin is the bottom of the banner.
   2491 The banner is 124 tall.
   2492 */
   2493 void SP_misc_ctf_small_banner (edict_t *ent)
   2494 {
   2495 	ent->movetype = MOVETYPE_NONE;
   2496 	ent->solid = SOLID_NOT;
   2497 	ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2");
   2498 	if (ent->spawnflags & 1) // team2
   2499 		ent->s.skinnum = 1;
   2500 
   2501 	ent->s.frame = rand() % 16;
   2502 	gi.linkentity (ent);
   2503 
   2504 	ent->think = misc_ctf_banner_think;
   2505 	ent->nextthink = level.time + FRAMETIME;
   2506 }
   2507 
   2508 /*-----------------------------------------------------------------------*/
   2509 
   2510 static void SetLevelName(pmenu_t *p)
   2511 {
   2512 	static char levelname[33];
   2513 
   2514 	levelname[0] = '*';
   2515 	if (g_edicts[0].message)
   2516 		strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2);
   2517 	else
   2518 		strncpy(levelname+1, level.mapname, sizeof(levelname) - 2);
   2519 	levelname[sizeof(levelname) - 1] = 0;
   2520 	p->text = levelname;
   2521 }
   2522 
   2523 
   2524 /*-----------------------------------------------------------------------*/
   2525 
   2526 
   2527 /* ELECTIONS */
   2528 
   2529 qboolean CTFBeginElection(edict_t *ent, elect_t type, char *msg)
   2530 {
   2531 	int i;
   2532 	int count;
   2533 	edict_t *e;
   2534 
   2535 	if (electpercentage->value == 0) {
   2536 		gi.cprintf(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n");
   2537 		return false;
   2538 	}
   2539 
   2540 
   2541 	if (ctfgame.election != ELECT_NONE) {
   2542 		gi.cprintf(ent, PRINT_HIGH, "Election already in progress.\n");
   2543 		return false;
   2544 	}
   2545 
   2546 	// clear votes
   2547 	count = 0;
   2548 	for (i = 1; i <= maxclients->value; i++) {
   2549 		e = g_edicts + i;
   2550 		e->client->resp.voted = false;
   2551 		if (e->inuse)
   2552 			count++;
   2553 	}
   2554 
   2555 	if (count < 2) {
   2556 		gi.cprintf(ent, PRINT_HIGH, "Not enough players for election.\n");
   2557 		return false;
   2558 	}
   2559 
   2560 	ctfgame.etarget = ent;
   2561 	ctfgame.election = type;
   2562 	ctfgame.evotes = 0;
   2563 	ctfgame.needvotes = (count * electpercentage->value) / 100;
   2564 	ctfgame.electtime = level.time + 20; // twenty seconds for election
   2565 	strncpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg) - 1);
   2566 
   2567 	// tell everyone
   2568 	gi.bprintf(PRINT_CHAT, "%s\n", ctfgame.emsg);
   2569 	gi.bprintf(PRINT_HIGH, "Type YES or NO to vote on this request.\n");
   2570 	gi.bprintf(PRINT_HIGH, "Votes: %d  Needed: %d  Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
   2571 		(int)(ctfgame.electtime - level.time));
   2572 
   2573 	return true;
   2574 }
   2575 
   2576 void DoRespawn (edict_t *ent);
   2577 
   2578 void CTFResetAllPlayers(void)
   2579 {
   2580 	int i;
   2581 	edict_t *ent;
   2582 
   2583 	for (i = 1; i <= maxclients->value; i++) {
   2584 		ent = g_edicts + i;
   2585 		if (!ent->inuse)
   2586 			continue;
   2587 
   2588 		if (ent->client->menu)
   2589 			PMenu_Close(ent);
   2590 
   2591 		CTFPlayerResetGrapple(ent);
   2592 		CTFDeadDropFlag(ent);
   2593 		CTFDeadDropTech(ent);
   2594 
   2595 		ent->client->resp.ctf_team = CTF_NOTEAM;
   2596 		ent->client->resp.ready = false;
   2597 
   2598 		ent->svflags = 0;
   2599 		ent->flags &= ~FL_GODMODE;
   2600 		PutClientInServer(ent);
   2601 	}
   2602 
   2603 	// reset the level
   2604 	CTFResetTech();
   2605 	CTFResetFlags();
   2606 
   2607 	for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) {
   2608 		if (ent->inuse && !ent->client) {
   2609 			if (ent->solid == SOLID_NOT && ent->think == DoRespawn &&
   2610 				ent->nextthink >= level.time) {
   2611 				ent->nextthink = 0;
   2612 				DoRespawn(ent);
   2613 			}
   2614 		}
   2615 	}
   2616 	if (ctfgame.match == MATCH_SETUP)
   2617 		ctfgame.matchtime = level.time + matchsetuptime->value * 60;
   2618 }
   2619 
   2620 void CTFAssignGhost(edict_t *ent)
   2621 {
   2622 	int ghost, i;
   2623 
   2624 	for (ghost = 0; ghost < MAX_CLIENTS; ghost++)
   2625 		if (!ctfgame.ghosts[ghost].code)
   2626 			break;
   2627 	if (ghost == MAX_CLIENTS)
   2628 		return;
   2629 	ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team;
   2630 	ctfgame.ghosts[ghost].score = 0;
   2631 	for (;;) {
   2632 		ctfgame.ghosts[ghost].code = 10000 + (rand() % 90000);
   2633 		for (i = 0; i < MAX_CLIENTS; i++)
   2634 			if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code)
   2635 				break;
   2636 		if (i == MAX_CLIENTS)
   2637 			break;
   2638 	}
   2639 	ctfgame.ghosts[ghost].ent = ent;
   2640 	strcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname);
   2641 	ent->client->resp.ghost = ctfgame.ghosts + ghost;
   2642 	gi.cprintf(ent, PRINT_CHAT, "Your ghost code is **** %d ****\n", ctfgame.ghosts[ghost].code);
   2643 	gi.cprintf(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score "
   2644 		"intact by typing \"ghost %d\".\n", ctfgame.ghosts[ghost].code);
   2645 }
   2646 
   2647 // start a match
   2648 void CTFStartMatch(void)
   2649 {
   2650 	int i;
   2651 	edict_t *ent;
   2652 	int ghost = 0;
   2653 
   2654 	ctfgame.match = MATCH_GAME;
   2655 	ctfgame.matchtime = level.time + matchtime->value * 60;
   2656 
   2657 	ctfgame.team1 = ctfgame.team2 = 0;
   2658 
   2659 	memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts));
   2660 
   2661 	for (i = 1; i <= maxclients->value; i++) {
   2662 		ent = g_edicts + i;
   2663 		if (!ent->inuse)
   2664 			continue;
   2665 
   2666 		ent->client->resp.score = 0;
   2667 		ent->client->resp.ctf_state = 0;
   2668 		ent->client->resp.ghost = NULL;
   2669 
   2670 		gi.centerprintf(ent, "******************\n\nMATCH HAS STARTED!\n\n******************");
   2671 
   2672 		if (ent->client->resp.ctf_team != CTF_NOTEAM) {
   2673 			// make up a ghost code
   2674 			CTFAssignGhost(ent);
   2675 			CTFPlayerResetGrapple(ent);
   2676 			ent->svflags = SVF_NOCLIENT;
   2677 			ent->flags &= ~FL_GODMODE;
   2678 
   2679 			ent->client->respawn_time = level.time + 1.0 + ((rand()%30)/10.0);
   2680 			ent->client->ps.pmove.pm_type = PM_DEAD;
   2681 			ent->client->anim_priority = ANIM_DEATH;
   2682 			ent->s.frame = FRAME_death308-1;
   2683 			ent->client->anim_end = FRAME_death308;
   2684 			ent->deadflag = DEAD_DEAD;
   2685 			ent->movetype = MOVETYPE_NOCLIP;
   2686 			ent->client->ps.gunindex = 0;
   2687 			gi.linkentity (ent);
   2688 		}
   2689 	}
   2690 }
   2691 
   2692 void CTFEndMatch(void)
   2693 {
   2694 	ctfgame.match = MATCH_POST;
   2695 	gi.bprintf(PRINT_CHAT, "MATCH COMPLETED!\n");
   2696 
   2697 	CTFCalcScores();
   2698 
   2699 	gi.bprintf(PRINT_HIGH, "RED TEAM:  %d captures, %d points\n",
   2700 		ctfgame.team1, ctfgame.total1);
   2701 	gi.bprintf(PRINT_HIGH, "BLUE TEAM:  %d captures, %d points\n",
   2702 		ctfgame.team2, ctfgame.total2);
   2703 
   2704 	if (ctfgame.team1 > ctfgame.team2)
   2705 		gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d CAPTURES!\n",
   2706 			ctfgame.team1 - ctfgame.team2);
   2707 	else if (ctfgame.team2 > ctfgame.team1)
   2708 		gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d CAPTURES!\n",
   2709 			ctfgame.team2 - ctfgame.team1);
   2710 	else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
   2711 		gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d POINTS!\n",
   2712 			ctfgame.total1 - ctfgame.total2);
   2713 	else if (ctfgame.total2 > ctfgame.total1) 
   2714 		gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d POINTS!\n",
   2715 			ctfgame.total2 - ctfgame.total1);
   2716 	else
   2717 		gi.bprintf(PRINT_CHAT, "TIE GAME!\n");
   2718 
   2719 	EndDMLevel();
   2720 }
   2721 
   2722 qboolean CTFNextMap(void)
   2723 {
   2724 	if (ctfgame.match == MATCH_POST) {
   2725 		ctfgame.match = MATCH_SETUP;
   2726 		CTFResetAllPlayers();
   2727 		return true;
   2728 	}
   2729 	return false;
   2730 }
   2731 
   2732 void CTFWinElection(void)
   2733 {
   2734 	switch (ctfgame.election) {
   2735 	case ELECT_MATCH :
   2736 		// reset into match mode
   2737 		if (competition->value < 3)
   2738 			gi.cvar_set("competition", "2");
   2739 		ctfgame.match = MATCH_SETUP;
   2740 		CTFResetAllPlayers();
   2741 		break;
   2742 
   2743 	case ELECT_ADMIN :
   2744 		ctfgame.etarget->client->resp.admin = true;
   2745 		gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ctfgame.etarget->client->pers.netname);
   2746 		gi.cprintf(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
   2747 		break;
   2748 
   2749 	case ELECT_MAP :
   2750 		gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", 
   2751 			ctfgame.etarget->client->pers.netname, ctfgame.elevel);
   2752 		strncpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap) - 1);
   2753 		EndDMLevel();
   2754 		break;
   2755 	}
   2756 	ctfgame.election = ELECT_NONE;
   2757 }
   2758 
   2759 void CTFVoteYes(edict_t *ent)
   2760 {
   2761 	if (ctfgame.election == ELECT_NONE) {
   2762 		gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n");
   2763 		return;
   2764 	}
   2765 	if (ent->client->resp.voted) {
   2766 		gi.cprintf(ent, PRINT_HIGH, "You already voted.\n");
   2767 		return;
   2768 	}
   2769 	if (ctfgame.etarget == ent) {
   2770 		gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n");
   2771 		return;
   2772 	}
   2773 
   2774 	ent->client->resp.voted = true;
   2775 
   2776 	ctfgame.evotes++;
   2777 	if (ctfgame.evotes == ctfgame.needvotes) {
   2778 		// the election has been won
   2779 		CTFWinElection();
   2780 		return;
   2781 	}
   2782 	gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg);
   2783 	gi.bprintf(PRINT_CHAT, "Votes: %d  Needed: %d  Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
   2784 		(int)(ctfgame.electtime - level.time));
   2785 }
   2786 
   2787 void CTFVoteNo(edict_t *ent)
   2788 {
   2789 	if (ctfgame.election == ELECT_NONE) {
   2790 		gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n");
   2791 		return;
   2792 	}
   2793 	if (ent->client->resp.voted) {
   2794 		gi.cprintf(ent, PRINT_HIGH, "You already voted.\n");
   2795 		return;
   2796 	}
   2797 	if (ctfgame.etarget == ent) {
   2798 		gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n");
   2799 		return;
   2800 	}
   2801 
   2802 	ent->client->resp.voted = true;
   2803 
   2804 	gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg);
   2805 	gi.bprintf(PRINT_CHAT, "Votes: %d  Needed: %d  Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
   2806 		(int)(ctfgame.electtime - level.time));
   2807 }
   2808 
   2809 void CTFReady(edict_t *ent)
   2810 {
   2811 	int i, j;
   2812 	edict_t *e;
   2813 	int t1, t2;
   2814 
   2815 	if (ent->client->resp.ctf_team == CTF_NOTEAM) {
   2816 		gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
   2817 		return;
   2818 	}
   2819 
   2820 	if (ctfgame.match != MATCH_SETUP) {
   2821 		gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
   2822 		return;
   2823 	}
   2824 
   2825 	if (ent->client->resp.ready) {
   2826 		gi.cprintf(ent, PRINT_HIGH, "You have already commited.\n");
   2827 		return;
   2828 	}
   2829 
   2830 	ent->client->resp.ready = true;
   2831 	gi.bprintf(PRINT_HIGH, "%s is ready.\n", ent->client->pers.netname);
   2832 
   2833 	t1 = t2 = 0;
   2834 	for (j = 0, i = 1; i <= maxclients->value; i++) {
   2835 		e = g_edicts + i;
   2836 		if (!e->inuse)
   2837 			continue;
   2838 		if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready)
   2839 			j++;
   2840 		if (e->client->resp.ctf_team == CTF_TEAM1)
   2841 			t1++;
   2842 		else if (e->client->resp.ctf_team == CTF_TEAM2)
   2843 			t2++;
   2844 	}
   2845 	if (!j && t1 && t2) {
   2846 		// everyone has commited
   2847 		gi.bprintf(PRINT_CHAT, "All players have commited.  Match starting\n");
   2848 		ctfgame.match = MATCH_PREGAME;
   2849 		ctfgame.matchtime = level.time + matchstarttime->value;
   2850 	}
   2851 }
   2852 
   2853 void CTFNotReady(edict_t *ent)
   2854 {
   2855 	if (ent->client->resp.ctf_team == CTF_NOTEAM) {
   2856 		gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
   2857 		return;
   2858 	}
   2859 
   2860 	if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME) {
   2861 		gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
   2862 		return;
   2863 	}
   2864 
   2865 	if (!ent->client->resp.ready) {
   2866 		gi.cprintf(ent, PRINT_HIGH, "You haven't commited.\n");
   2867 		return;
   2868 	}
   2869 
   2870 	ent->client->resp.ready = false;
   2871 	gi.bprintf(PRINT_HIGH, "%s is no longer ready.\n", ent->client->pers.netname);
   2872 
   2873 	if (ctfgame.match == MATCH_PREGAME) {
   2874 		gi.bprintf(PRINT_CHAT, "Match halted.\n");
   2875 		ctfgame.match = MATCH_SETUP;
   2876 		ctfgame.matchtime = level.time + matchsetuptime->value * 60;
   2877 	}
   2878 }
   2879 
   2880 void CTFGhost(edict_t *ent)
   2881 {
   2882 	int i;
   2883 	int n;
   2884 
   2885 	if (gi.argc() < 2) {
   2886 		gi.cprintf(ent, PRINT_HIGH, "Usage:  ghost <code>\n");
   2887 		return;
   2888 	}
   2889 
   2890 	if (ent->client->resp.ctf_team != CTF_NOTEAM) {
   2891 		gi.cprintf(ent, PRINT_HIGH, "You are already in the game.\n");
   2892 		return;
   2893 	}
   2894 	if (ctfgame.match != MATCH_GAME) {
   2895 		gi.cprintf(ent, PRINT_HIGH, "No match is in progress.\n");
   2896 		return;
   2897 	}
   2898 
   2899 	n = atoi(gi.argv(1));
   2900 
   2901 	for (i = 0; i < MAX_CLIENTS; i++) {
   2902 		if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n) {
   2903 			gi.cprintf(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n");
   2904 			ctfgame.ghosts[i].ent->client->resp.ghost = NULL;
   2905 			ent->client->resp.ctf_team = ctfgame.ghosts[i].team;
   2906 			ent->client->resp.ghost = ctfgame.ghosts + i;
   2907 			ent->client->resp.score = ctfgame.ghosts[i].score;
   2908 			ent->client->resp.ctf_state = 0;
   2909 			ctfgame.ghosts[i].ent = ent;
   2910 			ent->svflags = 0;
   2911 			ent->flags &= ~FL_GODMODE;
   2912 			PutClientInServer(ent);
   2913 			gi.bprintf(PRINT_HIGH, "%s has been reinstated to %s team.\n",
   2914 				ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team));
   2915 			return;
   2916 		}
   2917 	}
   2918 	gi.cprintf(ent, PRINT_HIGH, "Invalid ghost code.\n");
   2919 }
   2920 
   2921 qboolean CTFMatchSetup(void)
   2922 {
   2923 	if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME)
   2924 		return true;
   2925 	return false;
   2926 }
   2927 
   2928 qboolean CTFMatchOn(void)
   2929 {
   2930 	if (ctfgame.match == MATCH_GAME)
   2931 		return true;
   2932 	return false;
   2933 }
   2934 
   2935 
   2936 /*-----------------------------------------------------------------------*/
   2937 
   2938 void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p);
   2939 void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p);
   2940 void CTFCredits(edict_t *ent, pmenuhnd_t *p);
   2941 void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p);
   2942 void CTFChaseCam(edict_t *ent, pmenuhnd_t *p);
   2943 
   2944 pmenu_t creditsmenu[] = {
   2945 	{ "*Quake II",						PMENU_ALIGN_CENTER, NULL },
   2946 	{ "*ThreeWave Capture the Flag",	PMENU_ALIGN_CENTER, NULL },
   2947 	{ NULL,								PMENU_ALIGN_CENTER, NULL },
   2948 	{ "*Programming",					PMENU_ALIGN_CENTER, NULL }, 
   2949 	{ "Dave 'Zoid' Kirsch",				PMENU_ALIGN_CENTER, NULL },
   2950 	{ "*Level Design", 					PMENU_ALIGN_CENTER, NULL },
   2951 	{ "Christian Antkow",				PMENU_ALIGN_CENTER, NULL },
   2952 	{ "Tim Willits",					PMENU_ALIGN_CENTER, NULL },
   2953 	{ "Dave 'Zoid' Kirsch",				PMENU_ALIGN_CENTER, NULL },
   2954 	{ "*Art",							PMENU_ALIGN_CENTER, NULL },
   2955 	{ "Adrian Carmack Paul Steed",		PMENU_ALIGN_CENTER, NULL },
   2956 	{ "Kevin Cloud",					PMENU_ALIGN_CENTER, NULL },
   2957 	{ "*Sound",							PMENU_ALIGN_CENTER, NULL },
   2958 	{ "Tom 'Bjorn' Klok",				PMENU_ALIGN_CENTER, NULL },
   2959 	{ "*Original CTF Art Design",		PMENU_ALIGN_CENTER, NULL },
   2960 	{ "Brian 'Whaleboy' Cozzens",		PMENU_ALIGN_CENTER, NULL },
   2961 	{ NULL,								PMENU_ALIGN_CENTER, NULL },
   2962 	{ "Return to Main Menu",			PMENU_ALIGN_LEFT, CTFReturnToMain }
   2963 };
   2964 
   2965 static const int jmenu_level = 2;
   2966 static const int jmenu_match = 3;
   2967 static const int jmenu_red = 5;
   2968 static const int jmenu_blue = 7;
   2969 static const int jmenu_chase = 9;
   2970 static const int jmenu_reqmatch = 11;
   2971 
   2972 pmenu_t joinmenu[] = {
   2973 	{ "*Quake II",			PMENU_ALIGN_CENTER, NULL },
   2974 	{ "*ThreeWave Capture the Flag",	PMENU_ALIGN_CENTER, NULL },
   2975 	{ NULL,					PMENU_ALIGN_CENTER, NULL },
   2976 	{ NULL,					PMENU_ALIGN_CENTER, NULL },
   2977 	{ NULL,					PMENU_ALIGN_CENTER, NULL },
   2978 	{ "Join Red Team",		PMENU_ALIGN_LEFT, CTFJoinTeam1 },
   2979 	{ NULL,					PMENU_ALIGN_LEFT, NULL },
   2980 	{ "Join Blue Team",		PMENU_ALIGN_LEFT, CTFJoinTeam2 },
   2981 	{ NULL,					PMENU_ALIGN_LEFT, NULL },
   2982 	{ "Chase Camera",		PMENU_ALIGN_LEFT, CTFChaseCam },
   2983 	{ "Credits",			PMENU_ALIGN_LEFT, CTFCredits },
   2984 	{ NULL,					PMENU_ALIGN_LEFT, NULL },
   2985 	{ NULL,					PMENU_ALIGN_LEFT, NULL },
   2986 	{ "Use [ and ] to move cursor",	PMENU_ALIGN_LEFT, NULL },
   2987 	{ "ENTER to select",	PMENU_ALIGN_LEFT, NULL },
   2988 	{ "ESC to Exit Menu",	PMENU_ALIGN_LEFT, NULL },
   2989 	{ "(TAB to Return)",	PMENU_ALIGN_LEFT, NULL },
   2990 	{ "v" CTF_STRING_VERSION,	PMENU_ALIGN_RIGHT, NULL },
   2991 };
   2992 
   2993 pmenu_t nochasemenu[] = {
   2994 	{ "*Quake II",			PMENU_ALIGN_CENTER, NULL },
   2995 	{ "*ThreeWave Capture the Flag",	PMENU_ALIGN_CENTER, NULL },
   2996 	{ NULL,					PMENU_ALIGN_CENTER, NULL },
   2997 	{ NULL,					PMENU_ALIGN_CENTER, NULL },
   2998 	{ "No one to chase",	PMENU_ALIGN_LEFT, NULL },
   2999 	{ NULL,					PMENU_ALIGN_CENTER, NULL },
   3000 	{ "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain }
   3001 };
   3002 
   3003 void CTFJoinTeam(edict_t *ent, int desired_team)
   3004 {
   3005 	char *s;
   3006 
   3007 	PMenu_Close(ent);
   3008 
   3009 	ent->svflags &= ~SVF_NOCLIENT;
   3010 	ent->client->resp.ctf_team = desired_team;
   3011 	ent->client->resp.ctf_state = 0;
   3012 	s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
   3013 	CTFAssignSkin(ent, s);
   3014 
   3015 	// assign a ghost if we are in match mode
   3016 	if (ctfgame.match == MATCH_GAME) {
   3017 		if (ent->client->resp.ghost)
   3018 			ent->client->resp.ghost->code = 0;
   3019 		ent->client->resp.ghost = NULL;
   3020 		CTFAssignGhost(ent);
   3021 	}
   3022 
   3023 	PutClientInServer (ent);
   3024 	// add a teleportation effect
   3025 	ent->s.event = EV_PLAYER_TELEPORT;
   3026 	// hold in place briefly
   3027 	ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
   3028 	ent->client->ps.pmove.pm_time = 14;
   3029 	gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n",
   3030 		ent->client->pers.netname, CTFTeamName(desired_team));
   3031 
   3032 	if (ctfgame.match == MATCH_SETUP) {
   3033 		gi.centerprintf(ent,	"***********************\n"
   3034 								"Type \"ready\" in console\n"
   3035 								"to ready up.\n"
   3036 								"***********************");
   3037 	}
   3038 }
   3039 
   3040 void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p)
   3041 {
   3042 	CTFJoinTeam(ent, CTF_TEAM1);
   3043 }
   3044 
   3045 void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p)
   3046 {
   3047 	CTFJoinTeam(ent, CTF_TEAM2);
   3048 }
   3049 
   3050 void CTFChaseCam(edict_t *ent, pmenuhnd_t *p)
   3051 {
   3052 	int i;
   3053 	edict_t *e;
   3054 
   3055 	if (ent->client->chase_target) {
   3056 		ent->client->chase_target = NULL;
   3057 		PMenu_Close(ent);
   3058 		return;
   3059 	}
   3060 
   3061 	for (i = 1; i <= maxclients->value; i++) {
   3062 		e = g_edicts + i;
   3063 		if (e->inuse && e->solid != SOLID_NOT) {
   3064 			ent->client->chase_target = e;
   3065 			PMenu_Close(ent);
   3066 			ent->client->update_chase = true;
   3067 			return;
   3068 		}
   3069 	}
   3070 
   3071 	SetLevelName(nochasemenu + jmenu_level);
   3072 
   3073 	PMenu_Close(ent);
   3074 	PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), NULL);
   3075 }
   3076 
   3077 void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p)
   3078 {
   3079 	PMenu_Close(ent);
   3080 	CTFOpenJoinMenu(ent);
   3081 }
   3082 
   3083 void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p)
   3084 {
   3085 	char text[1024];
   3086 
   3087 	PMenu_Close(ent);
   3088 
   3089 	sprintf(text, "%s has requested to switch to competition mode.",
   3090 		ent->client->pers.netname);
   3091 	CTFBeginElection(ent, ELECT_MATCH, text);
   3092 }
   3093 
   3094 void DeathmatchScoreboard (edict_t *ent);
   3095 
   3096 void CTFShowScores(edict_t *ent, pmenu_t *p)
   3097 {
   3098 	PMenu_Close(ent);
   3099 
   3100 	ent->client->showscores = true;
   3101 	ent->client->showinventory = false;
   3102 	DeathmatchScoreboard (ent);
   3103 }
   3104 
   3105 int CTFUpdateJoinMenu(edict_t *ent)
   3106 {
   3107 	static char team1players[32];
   3108 	static char team2players[32];
   3109 	int num1, num2, i;
   3110 
   3111 	if (ctfgame.match >= MATCH_PREGAME && matchlock->value) {
   3112 		joinmenu[jmenu_red].text = "MATCH IS LOCKED";
   3113 		joinmenu[jmenu_red].SelectFunc = NULL;
   3114 		joinmenu[jmenu_blue].text = "  (entry is not permitted)";
   3115 		joinmenu[jmenu_blue].SelectFunc = NULL;
   3116 	} else {
   3117 		if (ctfgame.match >= MATCH_PREGAME) {
   3118 			joinmenu[jmenu_red].text = "Join Red MATCH Team";
   3119 			joinmenu[jmenu_blue].text = "Join Blue MATCH Team";
   3120 		} else {
   3121 			joinmenu[jmenu_red].text = "Join Red Team";
   3122 			joinmenu[jmenu_blue].text = "Join Blue Team";
   3123 		}
   3124 		joinmenu[jmenu_red].SelectFunc = CTFJoinTeam1;
   3125 		joinmenu[jmenu_blue].SelectFunc = CTFJoinTeam2;
   3126 	}
   3127 
   3128 	if (ctf_forcejoin->string && *ctf_forcejoin->string) {
   3129 		if (stricmp(ctf_forcejoin->string, "red") == 0) {
   3130 			joinmenu[jmenu_blue].text = NULL;
   3131 			joinmenu[jmenu_blue].SelectFunc = NULL;
   3132 		} else if (stricmp(ctf_forcejoin->string, "blue") == 0) {
   3133 			joinmenu[jmenu_red].text = NULL;
   3134 			joinmenu[jmenu_red].SelectFunc = NULL;
   3135 		}
   3136 	}
   3137 
   3138 	if (ent->client->chase_target)
   3139 		joinmenu[jmenu_chase].text = "Leave Chase Camera";
   3140 	else
   3141 		joinmenu[jmenu_chase].text = "Chase Camera";
   3142 
   3143 	SetLevelName(joinmenu + jmenu_level);
   3144 
   3145 	num1 = num2 = 0;
   3146 	for (i = 0; i < maxclients->value; i++) {
   3147 		if (!g_edicts[i+1].inuse)
   3148 			continue;
   3149 		if (game.clients[i].resp.ctf_team == CTF_TEAM1)
   3150 			num1++;
   3151 		else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
   3152 			num2++;
   3153 	}
   3154 
   3155 	sprintf(team1players, "  (%d players)", num1);
   3156 	sprintf(team2players, "  (%d players)", num2);
   3157 
   3158 	switch (ctfgame.match) {
   3159 	case MATCH_NONE :
   3160 		joinmenu[jmenu_match].text = NULL;
   3161 		break;
   3162 
   3163 	case MATCH_SETUP :
   3164 		joinmenu[jmenu_match].text = "*MATCH SETUP IN PROGRESS";
   3165 		break;
   3166 
   3167 	case MATCH_PREGAME :
   3168 		joinmenu[jmenu_match].text = "*MATCH STARTING";
   3169 		break;
   3170 
   3171 	case MATCH_GAME :
   3172 		joinmenu[jmenu_match].text = "*MATCH IN PROGRESS";
   3173 		break;
   3174 	}
   3175 
   3176 	if (joinmenu[jmenu_red].text)
   3177 		joinmenu[jmenu_red+1].text = team1players;
   3178 	else
   3179 		joinmenu[jmenu_red+1].text = NULL;
   3180 	if (joinmenu[jmenu_blue].text)
   3181 		joinmenu[jmenu_blue+1].text = team2players;
   3182 	else
   3183 		joinmenu[jmenu_blue+1].text = NULL;
   3184 
   3185 	joinmenu[jmenu_reqmatch].text = NULL;
   3186 	joinmenu[jmenu_reqmatch].SelectFunc = NULL;
   3187 	if (competition->value && ctfgame.match < MATCH_SETUP) {
   3188 		joinmenu[jmenu_reqmatch].text = "Request Match";
   3189 		joinmenu[jmenu_reqmatch].SelectFunc = CTFRequestMatch;
   3190 	}
   3191 	
   3192 	if (num1 > num2)
   3193 		return CTF_TEAM1;
   3194 	else if (num2 > num1)
   3195 		return CTF_TEAM2;
   3196 	return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2;
   3197 }
   3198 
   3199 void CTFOpenJoinMenu(edict_t *ent)
   3200 {
   3201 	int team;
   3202 
   3203 	team = CTFUpdateJoinMenu(ent);
   3204 	if (ent->client->chase_target)
   3205 		team = 8;
   3206 	else if (team == CTF_TEAM1)
   3207 		team = 4;
   3208 	else
   3209 		team = 6;
   3210 	PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), NULL);
   3211 }
   3212 
   3213 void CTFCredits(edict_t *ent, pmenuhnd_t *p)
   3214 {
   3215 	PMenu_Close(ent);
   3216 	PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), NULL);
   3217 }
   3218 
   3219 qboolean CTFStartClient(edict_t *ent)
   3220 {
   3221 	if (ent->client->resp.ctf_team != CTF_NOTEAM)
   3222 		return false;
   3223 
   3224 	if (!((int)dmflags->value & DF_CTF_FORCEJOIN) || ctfgame.match >= MATCH_SETUP) {
   3225 		// start as 'observer'
   3226 		ent->movetype = MOVETYPE_NOCLIP;
   3227 		ent->solid = SOLID_NOT;
   3228 		ent->svflags |= SVF_NOCLIENT;
   3229 		ent->client->resp.ctf_team = CTF_NOTEAM;
   3230 		ent->client->ps.gunindex = 0;
   3231 		gi.linkentity (ent);
   3232 
   3233 		CTFOpenJoinMenu(ent);
   3234 		return true;
   3235 	}
   3236 	return false;
   3237 }
   3238 
   3239 void CTFObserver(edict_t *ent)
   3240 {
   3241 	// start as 'observer'
   3242 	if (ent->movetype == MOVETYPE_NOCLIP) {
   3243 		gi.cprintf(ent, PRINT_HIGH, "You are already an observer.\n");
   3244 		return;
   3245 	}
   3246 
   3247 	CTFPlayerResetGrapple(ent);
   3248 	CTFDeadDropFlag(ent);
   3249 	CTFDeadDropTech(ent);
   3250 
   3251 	ent->movetype = MOVETYPE_NOCLIP;
   3252 	ent->solid = SOLID_NOT;
   3253 	ent->svflags |= SVF_NOCLIENT;
   3254 	ent->client->resp.ctf_team = CTF_NOTEAM;
   3255 	ent->client->ps.gunindex = 0;
   3256 	ent->client->resp.score = 0;
   3257 	gi.linkentity (ent);
   3258 	CTFOpenJoinMenu(ent);
   3259 }
   3260 
   3261 qboolean CTFInMatch(void)
   3262 {
   3263 	if (ctfgame.match > MATCH_NONE)
   3264 		return true;
   3265 	return false;
   3266 }
   3267 
   3268 qboolean CTFCheckRules(void)
   3269 {
   3270 	int t;
   3271 	int i, j;
   3272 	char text[64];
   3273 	edict_t *ent;
   3274 
   3275 	if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time) {
   3276 		gi.bprintf(PRINT_CHAT, "Election timed out and has been cancelled.\n");
   3277 		ctfgame.election = ELECT_NONE;
   3278 	}
   3279 
   3280 	if (ctfgame.match != MATCH_NONE) {
   3281 		t = ctfgame.matchtime - level.time;
   3282 
   3283 		if (t <= 0) { // time ended on something
   3284 			switch (ctfgame.match) {
   3285 			case MATCH_SETUP :
   3286 				// go back to normal mode
   3287 				if (competition->value < 3) {
   3288 					ctfgame.match = MATCH_NONE;
   3289 					gi.cvar_set("competition", "1");
   3290 					CTFResetAllPlayers();
   3291 				} else {
   3292 					// reset the time
   3293 					ctfgame.matchtime = level.time + matchsetuptime->value * 60;
   3294 				}
   3295 				return false;
   3296 
   3297 			case MATCH_PREGAME :
   3298 				// match started!
   3299 				CTFStartMatch();
   3300 				return false;
   3301 
   3302 			case MATCH_GAME :
   3303 				// match ended!
   3304 				CTFEndMatch();
   3305 				return false;
   3306 			}
   3307 		}
   3308 
   3309 		if (t == ctfgame.lasttime)
   3310 			return false;
   3311 
   3312 		ctfgame.lasttime = t;
   3313 
   3314 		switch (ctfgame.match) {
   3315 		case MATCH_SETUP :
   3316 			for (j = 0, i = 1; i <= maxclients->value; i++) {
   3317 				ent = g_edicts + i;
   3318 				if (!ent->inuse)
   3319 					continue;
   3320 				if (ent->client->resp.ctf_team != CTF_NOTEAM &&
   3321 					!ent->client->resp.ready)
   3322 					j++;
   3323 			}
   3324 
   3325 			if (competition->value < 3)
   3326 				sprintf(text, "%02d:%02d SETUP: %d not ready",
   3327 					t / 60, t % 60, j);
   3328 			else
   3329 				sprintf(text, "SETUP: %d not ready", j);
   3330 
   3331 			gi.configstring (CONFIG_CTF_MATCH, text);
   3332 			break;
   3333 
   3334 
   3335 		case MATCH_PREGAME :
   3336 			sprintf(text, "%02d:%02d UNTIL START",
   3337 				t / 60, t % 60);
   3338 			gi.configstring (CONFIG_CTF_MATCH, text);
   3339 			break;
   3340 
   3341 		case MATCH_GAME:
   3342 			sprintf(text, "%02d:%02d MATCH",
   3343 				t / 60, t % 60);
   3344 			gi.configstring (CONFIG_CTF_MATCH, text);
   3345 			break;
   3346 		}
   3347 		return false;
   3348 	}
   3349 
   3350 	if (capturelimit->value && 
   3351 		(ctfgame.team1 >= capturelimit->value ||
   3352 		ctfgame.team2 >= capturelimit->value)) {
   3353 		gi.bprintf (PRINT_HIGH, "Capturelimit hit.\n");
   3354 		return true;
   3355 	}
   3356 	return false;
   3357 }
   3358 
   3359 /*--------------------------------------------------------------------------
   3360  * just here to help old map conversions
   3361  *--------------------------------------------------------------------------*/
   3362 
   3363 static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
   3364 {
   3365 	edict_t		*dest;
   3366 	int			i;
   3367 	vec3_t		forward;
   3368 
   3369 	if (!other->client)
   3370 		return;
   3371 	dest = G_Find (NULL, FOFS(targetname), self->target);
   3372 	if (!dest)
   3373 	{
   3374 		gi.dprintf ("Couldn't find destination\n");
   3375 		return;
   3376 	}
   3377 
   3378 //ZOID
   3379 	CTFPlayerResetGrapple(other);
   3380 //ZOID
   3381 
   3382 	// unlink to make sure it can't possibly interfere with KillBox
   3383 	gi.unlinkentity (other);
   3384 
   3385 	VectorCopy (dest->s.origin, other->s.origin);
   3386 	VectorCopy (dest->s.origin, other->s.old_origin);
   3387 //	other->s.origin[2] += 10;
   3388 
   3389 	// clear the velocity and hold them in place briefly
   3390 	VectorClear (other->velocity);
   3391 	other->client->ps.pmove.pm_time = 160>>3;		// hold time
   3392 	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
   3393 
   3394 	// draw the teleport splash at source and on the player
   3395 	self->enemy->s.event = EV_PLAYER_TELEPORT;
   3396 	other->s.event = EV_PLAYER_TELEPORT;
   3397 
   3398 	// set angles
   3399 	for (i=0 ; i<3 ; i++)
   3400 		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
   3401 
   3402 	other->s.angles[PITCH] = 0;
   3403 	other->s.angles[YAW] = dest->s.angles[YAW];
   3404 	other->s.angles[ROLL] = 0;
   3405 	VectorCopy (dest->s.angles, other->client->ps.viewangles);
   3406 	VectorCopy (dest->s.angles, other->client->v_angle);
   3407 
   3408 	// give a little forward velocity
   3409 	AngleVectors (other->client->v_angle, forward, NULL, NULL);
   3410 	VectorScale(forward, 200, other->velocity);
   3411 
   3412 	// kill anything at the destination
   3413 	if (!KillBox (other))
   3414 	{
   3415 	}
   3416 
   3417 	gi.linkentity (other);
   3418 }
   3419 
   3420 /*QUAKED trigger_teleport (0.5 0.5 0.5) ?
   3421 Players touching this will be teleported
   3422 */
   3423 void SP_trigger_teleport (edict_t *ent)
   3424 {
   3425 	edict_t *s;
   3426 	int i;
   3427 
   3428 	if (!ent->target)
   3429 	{
   3430 		gi.dprintf ("teleporter without a target.\n");
   3431 		G_FreeEdict (ent);
   3432 		return;
   3433 	}
   3434 
   3435 	ent->svflags |= SVF_NOCLIENT;
   3436 	ent->solid = SOLID_TRIGGER;
   3437 	ent->touch = old_teleporter_touch;
   3438 	gi.setmodel (ent, ent->model);
   3439 	gi.linkentity (ent);
   3440 
   3441 	// noise maker and splash effect dude
   3442 	s = G_Spawn();
   3443 	ent->enemy = s;
   3444 	for (i = 0; i < 3; i++)
   3445 		s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2;
   3446 	s->s.sound = gi.soundindex ("world/hum1.wav");
   3447 	gi.linkentity(s);
   3448 	
   3449 }
   3450 
   3451 /*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32)
   3452 Point trigger_teleports at these.
   3453 */
   3454 void SP_info_teleport_destination (edict_t *ent)
   3455 {
   3456 	ent->s.origin[2] += 16;
   3457 }
   3458 
   3459 /*----------------------------------------------------------------------------------*/
   3460 /* ADMIN */
   3461 
   3462 typedef struct admin_settings_s {
   3463 	int matchlen;
   3464 	int matchsetuplen;
   3465 	int matchstartlen;
   3466 	qboolean weaponsstay;
   3467 	qboolean instantitems;
   3468 	qboolean quaddrop;
   3469 	qboolean instantweap;
   3470 	qboolean matchlock;
   3471 } admin_settings_t;
   3472 
   3473 #define SETMENU_SIZE (7 + 5)
   3474 
   3475 void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu);
   3476 void CTFOpenAdminMenu(edict_t *ent);
   3477 
   3478 void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p)
   3479 {
   3480 	admin_settings_t *settings = p->arg;
   3481 	char st[80];
   3482 	int i;
   3483 
   3484 	if (settings->matchlen != matchtime->value) {
   3485 		gi.bprintf(PRINT_HIGH, "%s changed the match length to %d minutes.\n",
   3486 			ent->client->pers.netname, settings->matchlen);
   3487 		if (ctfgame.match == MATCH_GAME) {
   3488 			// in the middle of a match, change it on the fly
   3489 			ctfgame.matchtime = (ctfgame.matchtime - matchtime->value*60) + settings->matchlen*60;
   3490 		} 
   3491 		sprintf(st, "%d", settings->matchlen);
   3492 		gi.cvar_set("matchtime", st);
   3493 	}
   3494 
   3495 	if (settings->matchsetuplen != matchsetuptime->value) {
   3496 		gi.bprintf(PRINT_HIGH, "%s changed the match setup time to %d minutes.\n",
   3497 			ent->client->pers.netname, settings->matchsetuplen);
   3498 		if (ctfgame.match == MATCH_SETUP) {
   3499 			// in the middle of a match, change it on the fly
   3500 			ctfgame.matchtime = (ctfgame.matchtime - matchsetuptime->value*60) + settings->matchsetuplen*60;
   3501 		} 
   3502 		sprintf(st, "%d", settings->matchsetuplen);
   3503 		gi.cvar_set("matchsetuptime", st);
   3504 	}
   3505 
   3506 	if (settings->matchstartlen != matchstarttime->value) {
   3507 		gi.bprintf(PRINT_HIGH, "%s changed the match start time to %d seconds.\n",
   3508 			ent->client->pers.netname, settings->matchstartlen);
   3509 		if (ctfgame.match == MATCH_PREGAME) {
   3510 			// in the middle of a match, change it on the fly
   3511 			ctfgame.matchtime = (ctfgame.matchtime - matchstarttime->value) + settings->matchstartlen;
   3512 		} 
   3513 		sprintf(st, "%d", settings->matchstartlen);
   3514 		gi.cvar_set("matchstarttime", st);
   3515 	}
   3516 
   3517 	if (settings->weaponsstay != !!((int)dmflags->value & DF_WEAPONS_STAY)) {
   3518 		gi.bprintf(PRINT_HIGH, "%s turned %s weapons stay.\n",
   3519 			ent->client->pers.netname, settings->weaponsstay ? "on" : "off");
   3520 		i = (int)dmflags->value;
   3521 		if (settings->weaponsstay)
   3522 			i |= DF_WEAPONS_STAY;
   3523 		else
   3524 			i &= ~DF_WEAPONS_STAY;
   3525 		sprintf(st, "%d", i);
   3526 		gi.cvar_set("dmflags", st);
   3527 	}
   3528 
   3529 	if (settings->instantitems != !!((int)dmflags->value & DF_INSTANT_ITEMS)) {
   3530 		gi.bprintf(PRINT_HIGH, "%s turned %s instant items.\n",
   3531 			ent->client->pers.netname, settings->instantitems ? "on" : "off");
   3532 		i = (int)dmflags->value;
   3533 		if (settings->instantitems)
   3534 			i |= DF_INSTANT_ITEMS;
   3535 		else
   3536 			i &= ~DF_INSTANT_ITEMS;
   3537 		sprintf(st, "%d", i);
   3538 		gi.cvar_set("dmflags", st);
   3539 	}
   3540 
   3541 	if (settings->quaddrop != !!((int)dmflags->value & DF_QUAD_DROP)) {
   3542 		gi.bprintf(PRINT_HIGH, "%s turned %s quad drop.\n",
   3543 			ent->client->pers.netname, settings->quaddrop ? "on" : "off");
   3544 		i = (int)dmflags->value;
   3545 		if (settings->quaddrop)
   3546 			i |= DF_QUAD_DROP;
   3547 		else
   3548 			i &= ~DF_QUAD_DROP;
   3549 		sprintf(st, "%d", i);
   3550 		gi.cvar_set("dmflags", st);
   3551 	}
   3552 
   3553 	if (settings->instantweap != !!((int)instantweap->value)) {
   3554 		gi.bprintf(PRINT_HIGH, "%s turned %s instant weapons.\n",
   3555 			ent->client->pers.netname, settings->instantweap ? "on" : "off");
   3556 		sprintf(st, "%d", (int)settings->instantweap);
   3557 		gi.cvar_set("instantweap", st);
   3558 	}
   3559 
   3560 	if (settings->matchlock != !!((int)matchlock->value)) {
   3561 		gi.bprintf(PRINT_HIGH, "%s turned %s match lock.\n",
   3562 			ent->client->pers.netname, settings->matchlock ? "on" : "off");
   3563 		sprintf(st, "%d", (int)settings->matchlock);
   3564 		gi.cvar_set("matchlock", st);
   3565 	}
   3566 
   3567 	PMenu_Close(ent);
   3568 	CTFOpenAdminMenu(ent);
   3569 }
   3570 
   3571 void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p)
   3572 {
   3573 	admin_settings_t *settings = p->arg;
   3574 
   3575 	PMenu_Close(ent);
   3576 	CTFOpenAdminMenu(ent);
   3577 }
   3578 
   3579 void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p)
   3580 {
   3581 	admin_settings_t *settings = p->arg;
   3582 
   3583 	settings->matchlen = (settings->matchlen % 60) + 5;
   3584 	if (settings->matchlen < 5)
   3585 		settings->matchlen = 5;
   3586 
   3587 	CTFAdmin_UpdateSettings(ent, p);
   3588 }
   3589 
   3590 void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p)
   3591 {
   3592 	admin_settings_t *settings = p->arg;
   3593 
   3594 	settings->matchsetuplen = (settings->matchsetuplen % 60) + 5;
   3595 	if (settings->matchsetuplen < 5)
   3596 		settings->matchsetuplen = 5;
   3597 
   3598 	CTFAdmin_UpdateSettings(ent, p);
   3599 }
   3600 
   3601 void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p)
   3602 {
   3603 	admin_settings_t *settings = p->arg;
   3604 
   3605 	settings->matchstartlen = (settings->matchstartlen % 600) + 10;
   3606 	if (settings->matchstartlen < 20)
   3607 		settings->matchstartlen = 20;
   3608 
   3609 	CTFAdmin_UpdateSettings(ent, p);
   3610 }
   3611 
   3612 void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p)
   3613 {
   3614 	admin_settings_t *settings = p->arg;
   3615 
   3616 	settings->weaponsstay = !settings->weaponsstay;
   3617 	CTFAdmin_UpdateSettings(ent, p);
   3618 }
   3619 
   3620 void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p)
   3621 {
   3622 	admin_settings_t *settings = p->arg;
   3623 
   3624 	settings->instantitems = !settings->instantitems;
   3625 	CTFAdmin_UpdateSettings(ent, p);
   3626 }
   3627 
   3628 void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p)
   3629 {
   3630 	admin_settings_t *settings = p->arg;
   3631 
   3632 	settings->quaddrop = !settings->quaddrop;
   3633 	CTFAdmin_UpdateSettings(ent, p);
   3634 }
   3635 
   3636 void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p)
   3637 {
   3638 	admin_settings_t *settings = p->arg;
   3639 
   3640 	settings->instantweap = !settings->instantweap;
   3641 	CTFAdmin_UpdateSettings(ent, p);
   3642 }
   3643 
   3644 void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p)
   3645 {
   3646 	admin_settings_t *settings = p->arg;
   3647 
   3648 	settings->matchlock = !settings->matchlock;
   3649 	CTFAdmin_UpdateSettings(ent, p);
   3650 }
   3651 
   3652 void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu)
   3653 {
   3654 	int i = 2;
   3655 	char text[64];
   3656 	admin_settings_t *settings = setmenu->arg;
   3657 
   3658 	sprintf(text, "Match Len:       %2d mins", settings->matchlen);
   3659 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen);
   3660 	i++;
   3661 
   3662 	sprintf(text, "Match Setup Len: %2d mins", settings->matchsetuplen);
   3663 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen);
   3664 	i++;
   3665 
   3666 	sprintf(text, "Match Start Len: %2d secs", settings->matchstartlen);
   3667 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen);
   3668 	i++;
   3669 
   3670 	sprintf(text, "Weapons Stay:    %s", settings->weaponsstay ? "Yes" : "No");
   3671 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay);
   3672 	i++;
   3673 
   3674 	sprintf(text, "Instant Items:   %s", settings->instantitems ? "Yes" : "No");
   3675 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems);
   3676 	i++;
   3677 
   3678 	sprintf(text, "Quad Drop:       %s", settings->quaddrop ? "Yes" : "No");
   3679 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop);
   3680 	i++;
   3681 
   3682 	sprintf(text, "Instant Weapons: %s", settings->instantweap ? "Yes" : "No");
   3683 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap);
   3684 	i++;
   3685 
   3686 	sprintf(text, "Match Lock:      %s", settings->matchlock ? "Yes" : "No");
   3687 	PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock);
   3688 	i++;
   3689 
   3690 	PMenu_Update(ent);
   3691 }
   3692 
   3693 pmenu_t def_setmenu[] = {
   3694 	{ "*Settings Menu", PMENU_ALIGN_CENTER, NULL },
   3695 	{ NULL,				PMENU_ALIGN_CENTER, NULL },
   3696 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//int matchlen;         
   3697 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//int matchsetuplen;    
   3698 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//int matchstartlen;    
   3699 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//qboolean weaponsstay; 
   3700 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//qboolean instantitems;
   3701 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//qboolean quaddrop;    
   3702 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//qboolean instantweap; 
   3703 	{ NULL,				PMENU_ALIGN_LEFT, NULL },	//qboolean matchlock; 
   3704 	{ NULL,				PMENU_ALIGN_LEFT, NULL },
   3705 	{ "Apply",			PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply },
   3706 	{ "Cancel",			PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel }
   3707 };
   3708 
   3709 void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p)
   3710 {
   3711 	admin_settings_t *settings;
   3712 	pmenuhnd_t *menu;
   3713 
   3714 	PMenu_Close(ent);
   3715 
   3716 	settings = malloc(sizeof(*settings));
   3717 
   3718 	settings->matchlen = matchtime->value;
   3719 	settings->matchsetuplen = matchsetuptime->value;
   3720 	settings->matchstartlen = matchstarttime->value;
   3721 	settings->weaponsstay = !!((int)dmflags->value & DF_WEAPONS_STAY);
   3722 	settings->instantitems = !!((int)dmflags->value & DF_INSTANT_ITEMS);
   3723 	settings->quaddrop = !!((int)dmflags->value & DF_QUAD_DROP);
   3724 	settings->instantweap = instantweap->value != 0;
   3725 	settings->matchlock = matchlock->value != 0;
   3726 
   3727 	menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings);
   3728 	CTFAdmin_UpdateSettings(ent, menu);
   3729 }
   3730 
   3731 void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p)
   3732 {
   3733 	PMenu_Close(ent);
   3734 
   3735 	if (ctfgame.match == MATCH_SETUP) {
   3736 		gi.bprintf(PRINT_CHAT, "Match has been forced to start.\n");
   3737 		ctfgame.match = MATCH_PREGAME;
   3738 		ctfgame.matchtime = level.time + matchstarttime->value;
   3739 	} else if (ctfgame.match == MATCH_GAME) {
   3740 		gi.bprintf(PRINT_CHAT, "Match has been forced to terminate.\n");
   3741 		ctfgame.match = MATCH_SETUP;
   3742 		ctfgame.matchtime = level.time + matchsetuptime->value * 60;
   3743 		CTFResetAllPlayers();
   3744 	}
   3745 }
   3746 
   3747 void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p)
   3748 {
   3749 	PMenu_Close(ent);
   3750 
   3751 	if (ctfgame.match != MATCH_SETUP) {
   3752 		if (competition->value < 3)
   3753 			gi.cvar_set("competition", "2");
   3754 		ctfgame.match = MATCH_SETUP;
   3755 		CTFResetAllPlayers();
   3756 	}
   3757 }
   3758 
   3759 void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p)
   3760 {
   3761 	PMenu_Close(ent);
   3762 }
   3763 
   3764 
   3765 pmenu_t adminmenu[] = {
   3766 	{ "*Administration Menu",	PMENU_ALIGN_CENTER, NULL },
   3767 	{ NULL,						PMENU_ALIGN_CENTER, NULL }, // blank
   3768 	{ "Settings",				PMENU_ALIGN_LEFT, CTFAdmin_Settings },
   3769 	{ NULL,						PMENU_ALIGN_LEFT, NULL },
   3770 	{ NULL,						PMENU_ALIGN_LEFT, NULL },
   3771 	{ "Cancel",					PMENU_ALIGN_LEFT, CTFAdmin_Cancel },
   3772 	{ NULL,						PMENU_ALIGN_CENTER, NULL },
   3773 };
   3774 
   3775 void CTFOpenAdminMenu(edict_t *ent)
   3776 {
   3777 	adminmenu[3].text = NULL;
   3778 	adminmenu[3].SelectFunc = NULL;
   3779 	if (ctfgame.match == MATCH_SETUP) {
   3780 		adminmenu[3].text = "Force start match";
   3781 		adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
   3782 	} else if (ctfgame.match == MATCH_GAME) {
   3783 		adminmenu[3].text = "Cancel match";
   3784 		adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
   3785 	} else if (ctfgame.match == MATCH_NONE && competition->value) {
   3786 		adminmenu[3].text = "Switch to match mode";
   3787 		adminmenu[3].SelectFunc = CTFAdmin_MatchMode;
   3788 	}
   3789 
   3790 //	if (ent->client->menu)
   3791 //		PMenu_Close(ent->client->menu);
   3792 
   3793 	PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), NULL);
   3794 }
   3795 
   3796 void CTFAdmin(edict_t *ent)
   3797 {
   3798 	char text[1024];
   3799 
   3800 	if (gi.argc() > 1 && admin_password->string && *admin_password->string &&
   3801 		!ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0) {
   3802 		ent->client->resp.admin = true;
   3803 		gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ent->client->pers.netname);
   3804 		gi.cprintf(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
   3805 	}
   3806 
   3807 	if (!ent->client->resp.admin) {
   3808 		sprintf(text, "%s has requested admin rights.",
   3809 			ent->client->pers.netname);
   3810 		CTFBeginElection(ent, ELECT_ADMIN, text);
   3811 		return;
   3812 	}
   3813 
   3814 	if (ent->client->menu)
   3815 		PMenu_Close(ent);
   3816 
   3817 	CTFOpenAdminMenu(ent);
   3818 }
   3819 
   3820 /*----------------------------------------------------------------*/
   3821 
   3822 void CTFStats(edict_t *ent)
   3823 {
   3824 	int i, e;
   3825 	ghost_t *g;
   3826 	char st[80];
   3827 	char text[1400];
   3828 	edict_t *e2;
   3829 
   3830 	*text = 0;
   3831 	if (ctfgame.match == MATCH_SETUP) {
   3832 		for (i = 1; i <= maxclients->value; i++) {
   3833 			e2 = g_edicts + i;
   3834 			if (!e2->inuse)
   3835 				continue;
   3836 			if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) {
   3837 				sprintf(st, "%s is not ready.\n", e2->client->pers.netname);
   3838 				if (strlen(text) + strlen(st) < sizeof(text) - 50)
   3839 					strcat(text, st);
   3840 			}
   3841 		}
   3842 	}
   3843 
   3844 	for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++)
   3845 		if (g->ent)
   3846 			break;
   3847 
   3848 	if (i == MAX_CLIENTS) {
   3849 		if (*text)
   3850 			gi.cprintf(ent, PRINT_HIGH, "%s", text);
   3851 		gi.cprintf(ent, PRINT_HIGH, "No statistics available.\n");
   3852 		return;
   3853 	}
   3854 
   3855 	strcat(text, "  #|Name            |Score|Kills|Death|BasDf|CarDf|Effcy|\n");
   3856 
   3857 	for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) {
   3858 		if (!*g->netname)
   3859 			continue;
   3860 
   3861 		if (g->deaths + g->kills == 0)
   3862 			e = 50;
   3863 		else
   3864 			e = g->kills * 100 / (g->kills + g->deaths);
   3865 		sprintf(st, "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n",
   3866 			g->number, 
   3867 			g->netname, 
   3868 			g->score, 
   3869 			g->kills, 
   3870 			g->deaths, 
   3871 			g->basedef,
   3872 			g->carrierdef, 
   3873 			e);
   3874 		if (strlen(text) + strlen(st) > sizeof(text) - 50) {
   3875 			sprintf(text+strlen(text), "And more...\n");
   3876 			gi.cprintf(ent, PRINT_HIGH, "%s", text);
   3877 			return;
   3878 		}
   3879 		strcat(text, st);
   3880 	}
   3881 	gi.cprintf(ent, PRINT_HIGH, "%s", text);
   3882 }
   3883 
   3884 void CTFPlayerList(edict_t *ent)
   3885 {
   3886 	int i;
   3887 	char st[80];
   3888 	char text[1400];
   3889 	edict_t *e2;
   3890 
   3891 	*text = 0;
   3892 	if (ctfgame.match == MATCH_SETUP) {
   3893 		for (i = 1; i <= maxclients->value; i++) {
   3894 			e2 = g_edicts + i;
   3895 			if (!e2->inuse)
   3896 				continue;
   3897 			if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) {
   3898 				sprintf(st, "%s is not ready.\n", e2->client->pers.netname);
   3899 				if (strlen(text) + strlen(st) < sizeof(text) - 50)
   3900 					strcat(text, st);
   3901 			}
   3902 		}
   3903 	}
   3904 
   3905 	// number, name, connect time, ping, score, admin
   3906 
   3907 	*text = 0;
   3908 	for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) {
   3909 		if (!e2->inuse)
   3910 			continue;
   3911 
   3912 		sprintf(st, "%3d %-16.16s %02d:%02d %4d %3d%s%s\n",
   3913 			i + 1,
   3914 			e2->client->pers.netname,
   3915 			(level.framenum - e2->client->resp.enterframe) / 600,
   3916 			((level.framenum - e2->client->resp.enterframe) % 600)/10,
   3917 			e2->client->ping,
   3918 			e2->client->resp.score,
   3919 			(ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ?
   3920 			(e2->client->resp.ready ? " (ready)" : " (notready)") : "",
   3921 			e2->client->resp.admin ? " (admin)" : "");
   3922 		if (strlen(text) + strlen(st) > sizeof(text) - 50) {
   3923 			sprintf(text+strlen(text), "And more...\n");
   3924 			gi.cprintf(ent, PRINT_HIGH, "%s", text);
   3925 			return;
   3926 		}
   3927 		strcat(text, st);
   3928 	}
   3929 	gi.cprintf(ent, PRINT_HIGH, "%s", text);
   3930 }
   3931 
   3932 
   3933 void CTFWarp(edict_t *ent)
   3934 {
   3935 	char text[1024];
   3936 	char *mlist, *token;
   3937 	static const char *seps = " \t\n\r";
   3938 
   3939 	if (gi.argc() < 2) {
   3940 		gi.cprintf(ent, PRINT_HIGH, "Where do you want to warp to?\n");
   3941 		gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string);
   3942 		return;
   3943 	}
   3944 
   3945 	mlist = strdup(warp_list->string);
   3946 
   3947 	token = strtok(mlist, seps);
   3948 	while (token != NULL) {
   3949 		if (Q_stricmp(token, gi.argv(1)) == 0)
   3950 			break;
   3951 		token = strtok(NULL, seps);
   3952 	}
   3953 
   3954 	if (token == NULL) {
   3955 		gi.cprintf(ent, PRINT_HIGH, "Unknown CTF level.\n");
   3956 		gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string);
   3957 		free(mlist);
   3958 		return;
   3959 	}
   3960 
   3961 	free(mlist);
   3962 
   3963 
   3964 	if (ent->client->resp.admin) {
   3965 		gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", 
   3966 			ent->client->pers.netname, gi.argv(1));
   3967 		strncpy(level.forcemap, gi.argv(1), sizeof(level.forcemap) - 1);
   3968 		EndDMLevel();
   3969 		return;
   3970 	}
   3971 
   3972 	sprintf(text, "%s has requested warping to level %s.", 
   3973 			ent->client->pers.netname, gi.argv(1));
   3974 	if (CTFBeginElection(ent, ELECT_MAP, text))
   3975 		strncpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel) - 1);
   3976 }
   3977 
   3978 void CTFBoot(edict_t *ent)
   3979 {
   3980 	int i;
   3981 	edict_t *targ;
   3982 	char text[80];
   3983 
   3984 	if (!ent->client->resp.admin) {
   3985 		gi.cprintf(ent, PRINT_HIGH, "You are not an admin.\n");
   3986 		return;
   3987 	}
   3988 
   3989 	if (gi.argc() < 2) {
   3990 		gi.cprintf(ent, PRINT_HIGH, "Who do you want to kick?\n");
   3991 		return;
   3992 	}
   3993 
   3994 	if (*gi.argv(1) < '0' && *gi.argv(1) > '9') {
   3995 		gi.cprintf(ent, PRINT_HIGH, "Specify the player number to kick.\n");
   3996 		return;
   3997 	}
   3998 
   3999 	i = atoi(gi.argv(1));
   4000 	if (i < 1 || i > maxclients->value) {
   4001 		gi.cprintf(ent, PRINT_HIGH, "Invalid player number.\n");
   4002 		return;
   4003 	}
   4004 
   4005 	targ = g_edicts + i;
   4006 	if (!targ->inuse) {
   4007 		gi.cprintf(ent, PRINT_HIGH, "That player number is not connected.\n");
   4008 		return;
   4009 	}
   4010 
   4011 	sprintf(text, "kick %d\n", i - 1);
   4012 	gi.AddCommandString(text);
   4013 }
   4014 
   4015 
   4016