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