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