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